diff --git a/web/CXM_jquery.anubis b/web/CXM_jquery.anubis
index 24887db..607e971 100644
--- a/web/CXM_jquery.anubis
+++ b/web/CXM_jquery.anubis
@@ -3,7 +3,7 @@
* User: Jeremy
* Date: 21/08/2012
* Time: 16:52
- *
+ *
*/
read calexium_lib/web/CXM_making_a_web_site.anubis
@@ -26,6 +26,69 @@ public type JQuerySelector:
element_class(String name),
childs(HtmlId id).
+define String
+ jquery_button_get_onclick_action_window_options
+ (
+ List(Other_Window_Option) options,
+ String formated_options
+ )
+ =
+ //if there is already formated_options add a comma + space between the options
+ with comma = if formated_options="" then "" else ", ",
+
+ if options is
+ {
+ [ ] then formated_options,
+ [ h . t ] then
+ if h is
+ {
+ resizable then jquery_button_get_onclick_action_window_options(
+ t, formated_options+ comma+ "resizable=yes" ),
+
+ scrollbars then jquery_button_get_onclick_action_window_options(
+ t, formated_options+ comma + "scrollbars=yes"),
+
+ width(w) then jquery_button_get_onclick_action_window_options(
+ t, formated_options+ comma + "width="+w),
+
+ height(h) then jquery_button_get_onclick_action_window_options(
+ t, formated_options+ comma + "height="+h)
+ }
+ }
+ .
+
+public define String
+ jquery_button_get_onclick_action
+ (
+ JQuery_Actioner actioner
+ )
+ =
+ if actioner is jQuery_actioner(target, act_type, str) then
+ if target is
+ {
+ same then
+ if act_type is
+ {
+ jqform then "$('#"+str+"').submit();",
+ jqlink then "document.location='/?a="+str+"';",
+ jqscript then str
+ },
+ same(label) then
+ if act_type is
+ {
+ jqform then "$('#"+str+"').submit();",
+ jqlink then "document.location='/?a="+str+"';",
+ jqscript then str
+ },
+ other(window_name, window_options) then
+ if act_type is
+ {
+ jqform then "$('#"+str+"').submit();", //il s'agit d'une soumision d'un formulaire => cela se fait uniquement dans la même fenêtre !
+ jqlink then "window.open('"+str+"', '"+window_name+"', '"+jquery_button_get_onclick_action_window_options(window_options, "")+"');",
+ jqscript then str
+ }
+ }.
+
public define String
format_jquery_selector
(
@@ -35,10 +98,10 @@ public define String
{
element(html_id) then
if html_id is htmlId(id) then "'#" + id + "'",
-
+
element_class(name) then
"'." + name + "'",
-
+
childs(html_id) then
if html_id is htmlId(id) then "'#" + id + " > *'"
}.
@@ -50,14 +113,14 @@ public define Script
String code //javascript code to execute when the document is ready
)=
script("text/javascript","$(document).ready(function(){"+code+"});").
-
+
public define String
jquery_ready
(
String code //javascript code to execute when the document is ready
)=
"$(document).ready(function(){"+code+"});".
-
+
/* jQuery default js files */
define List(HTML_Head_Tag)
jquery_defaults
@@ -66,7 +129,7 @@ define List(HTML_Head_Tag)
js(js_file("/js/jquery/jquery-1.11.3.js")),
js(js_file("/js/jquery/jquery-ui-1.11.4/jquery-ui.min.js")),
].
-
+
/* Initialize jQuery in compatiblity mode with jQueryUI */
public define List(HTML_Head_Tag)
jquery_init
diff --git a/web/CXM_json.anubis b/web/CXM_json.anubis
index 30bb328..e9a8cec 100644
--- a/web/CXM_json.anubis
+++ b/web/CXM_json.anubis
@@ -3,7 +3,7 @@
* User: ricard
* Date: 18/08/2008
* Time: 08:12
- *
+ *
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
@@ -38,7 +38,7 @@ public define JsonMember
List(JsonMember) value,
) =
json_member(name, json_object(value)).
-
+
public define JsonMember
json_member
@@ -72,7 +72,7 @@ public define JsonMember
String value,
) =
json_member(name, json_string(value)).
-
+
public define JsonMember
json_member
(
@@ -123,7 +123,7 @@ public define Printable_tree
(
JsonValue json
).
-
+
public define Printable_tree
to_javascript
(
@@ -137,7 +137,7 @@ public define Printable_tree
) =
if member is json_member(name, value) then
[ "\"", name, "\":" . format_json(value) ].
-
+
public define Printable_tree
json_member_to_js
(
@@ -151,18 +151,18 @@ define Printable_tree
(
List(JsonMember) members
) =
- if members is
+ if members is
{
[] then [],
[h . t] then [ format_json_member(h), (if t is [] then "" else ",") . format_json_object(t) ]
}.
-
+
define Printable_tree
json_object_to_js
(
List(JsonMember) members
) =
- if members is
+ if members is
{
[] then [],
[h . t] then [ json_member_to_js(h), (if t is [] then "" else ",") . json_object_to_js(t) ]
@@ -173,18 +173,18 @@ define Printable_tree
(
List(JsonValue) values
) =
- if values is
+ if values is
{
[] then [],
[h . t] then [ format_json(h), (if t is [] then "" else ",") . format_json_array(t) ]
}.
-
+
define Printable_tree
json_array_to_js
(
List(JsonValue) values
) =
- if values is
+ if values is
{
[] then [],
[h . t] then [ to_javascript(h), (if t is [] then "" else ",") . format_json_array(t) ]
@@ -205,7 +205,7 @@ public define Printable_tree
json_bool(b) then str_pt(if b then "true" else "false", []),
json_null then str_pt("null", [])
}.
-
+
public define Printable_tree
to_javascript
(
@@ -221,26 +221,25 @@ public define Printable_tree
json_bool(b) then str_pt(if b then "true" else "false", []),
json_null then str_pt("null", [])
}.
-
+
public define String
format_json
(
JsonValue json
) =
to_String(format_json(json)).
-
+
public define String
to_javascript
(
JsonValue json
) =
to_String(to_javascript(json)).
-
-public define WriteFileResult
+
+public define WriteFileResult
write_to_file
(
String filename, //filename where the JSON will be dump
JsonValue json //JSON to dump
)=
write_to_file(filename, format_json(json)).
-
diff --git a/web/CXM_multihost_http_server.anubis b/web/CXM_multihost_http_server.anubis
index 5549575..b177657 100644
--- a/web/CXM_multihost_http_server.anubis
+++ b/web/CXM_multihost_http_server.anubis
@@ -1,22 +1,22 @@
*Project* The Anubis Project
-
+
*Title* A Multi Host HTTP/HTTPS Server
-
- *Copyright* Copyright (c) Anubis Team 2003-2007.
+
+ *Copyright* Copyright (c) Anubis Team 2003-2007.
*Authors* Alain Prouté
David René
Cédric Ricard
-
- *Revised*
+
+ *Revised*
June 2015 : Optimization of the file upload
July 2007 : Initial version in calexium_lib
-
-
- *Overviews*
+
+
+ *Overviews*
In this file a HTTP/HTTPS server is defined, which is able to handle multiple hosts
(virtual hosts). It answers HTTP/HTTPS requests, sends files (images or any other kind
of file), constructs HTML pages on the fly using informations received from the client
@@ -30,46 +30,46 @@
----------------------------------- Table of Contents ---------------------------------
*** (1) Multihosting and redirections.
- *** (2) The incompatibility between SSL and virtual hosts.
+ *** (2) The incompatibility between SSL and virtual hosts.
*** (3) HTTP headers and web arguments.
- *** (4) Site descriptions.
- *** (5) Protection against denial of service attacks.
- *** (6) Starting your HTTP and HTTPS servers.
- *** (7) Private download.
- *** (8) About web argument names.
- *** (9) A web dispatcher.
+ *** (4) Site descriptions.
+ *** (5) Protection against denial of service attacks.
+ *** (6) Starting your HTTP and HTTPS servers.
+ *** (7) Private download.
+ *** (8) About web argument names.
+ *** (9) A web dispatcher.
*** (10) HTTP Errors
-
+
---------------------------------------------------------------------------------------
-
-
-
+
+
+
*** (1) Multihosting and redirections.
-
+
This HTTP/HTTPS server can handle several host (also called 'virtual hosts'), in other
words, you may have several sites on the same server, with the same IP address and same
port numbers, but distinct 'host names'.
-
+
A HTTP request sent by a browser contains the following informations:
-
- - a 'host name',
+
+ - a 'host name',
- an URI (Uniform Resource Identifier),
- - HTTP headers,
- - web arguments (in the form 'name=value').
-
+ - HTTP headers,
+ - web arguments (in the form 'name=value').
+
Actually, the host name is just the value of the HTTP header whose name is 'Host'. The
host name indicates which site is requested. Hence, it is the primary information for
branching to the right site. If there is no 'Host' HTTP header in the request, the
request is denied.
-
+
From now on, we may assume that the host is determined, and consequently that we are
concerned by only one site. Each site has his own directories on the server's
- disk.
-
+ disk.
+
Each site also has a list of 'redirections'. A redirection is a triplet, like this one:
-
- redirect("/", "www.our-business.com", "/homepage.awp")
+
+ redirect("/", "www.our-business.com", "/homepage.awp")
meaning that if the host is "www.our-business.com", and if the requested URI is "/",
then the URI to be served is "/homepage.awp". 'redirect' is a constructor of the type
@@ -81,12 +81,12 @@
like ".jpg", ".png", ".txt", etc... and represents a file path relative to the
'public' directory of the site. If these conditions are satisfied, the file is sent to
the client. Known extensions are recorded in 'web/mime.anubis'.
-
-
-
- *** (2) The incompatibility between SSL and virtual hosts.
-
+
+
+
+ *** (2) The incompatibility between SSL and virtual hosts.
+
Handling virtual hosts makes a problem under SSL (i.e. when using HTTPS), which is due
to the fact that the guys at Netscape who designed SSL probably did not have the
question of virtual hosts in mind. Indeed, the SSL handshake is completed before the
@@ -96,7 +96,7 @@
of the requested host. The user will have to accept the certificate manually, which is
not good for the security image of the site. This problem has at least two solutions
(as far as Anubis is concerned).
-
+
Solution 1. Arrange so that the network interface on which the server is listening
has at least as many different IP addresses as you have virtual hosts. Such
supplementary IP addresses are called 'IP Aliases'. In this case, start one HTTPS
@@ -105,7 +105,7 @@
'anbexec' as you have virtual hosts, because each instance of 'anbexec' can handle only
one server certificate. Of course, getting IP aliases is another problem to be solved
with your Internet provider.
-
+
Solution 2. We propose a simple solution, using only one server certificate (hence
only one instance of 'anbexec'). Since, we have only one server certificate, we must
introduce a notion of 'main host', i.e. a host containing all other 'virtual
@@ -114,48 +114,48 @@
that the main host redirects him to the right virtual host. Actually, the process will
be transparent to the client, except that the client will see the name of the main host
instead of the name of the virtual host in the 'location' field of the browser.
-
+
So, assume that the name of main host is 'www.securedhost.com', and that the names of
the virtual hosts are:
-
+
actual name simplified name
-----------------------------------------------------
www.virtual1.com virtual1
www.virtual2.com virtual2
www.virtual3.com virtual3
-
+
Then the (confidential) document '/doc/my_document.pdf' on 'www.virtual2.com' will have
the URL:
-
+
https://www.securedhost.com/virtual2/doc/my_document.pdf
-
+
In order to work transparently, this solution must combine HTTP and HTTPS. Indeed, the
vitual host must have a first page reachable under HTTP, through the URL:
-
+
http://www.virtual2.com/
-
+
The HTTP server will redirect this URL to the awp handler of virtual host 'virtual2'.
The handler of this virtual host is able to generate a first page containing the
following HTML meta:
-
+
,
-
+
so that the client is immediately redirected to the main host under HTTPS (hence
accepting tranparently the server certificate). The awp handler of 'virtual2' then
redirects this URL to the home page (maybe a login page) of 'virtual2'.
-
- See 'web/making_a_web_site.anubis' for the sequel of this story.
-
-
-
-
-
+
+ See 'web/making_a_web_site.anubis' for the sequel of this story.
+
+
+
+
+
*** (3) HTTP headers and web arguments.
-
+
Each HTTP request which arrives on the server contains a request line followed by a
series of HTTP headers. Each HTTP header is a pair '(name,value)' assigning a value to
- a name. The type 'HTTP_header' is defined in 'web/common.anubis'.
-
+ a name. The type 'HTTP_header' is defined in 'web/common.anubis'.
+
The request may also have a 'body'. The body contains either 'web arguments' or
uploaded files (or both). The request line itself may also contain web arguments (in a
so-called 'query string'). Like HTTP headers, 'web arguments' are pairs
@@ -163,26 +163,26 @@
which the client clicks, while HTTP headers are generated by the browser itself. The
type 'Web_arg' is defined in 'web/common.anubis'. It has two alternatives, one for
ordinary web arguments (pairs) and one for uploaded files.
-
-read CXM_common.anubis
+
+read CXM_common.anubis
read tools/basis.anubis
read tools/printable_tree.anubis
read system/string.anubis
read system/files.anubis
read system/lists.anubis
read system/data_io.anubis
-read web/mime.anubis
-
-
-
- *** (4) Site descriptions.
+read web/mime.anubis
+
+
+
+ *** (4) Site descriptions.
The type HTTP_Info gathers informations comming along with the client's request. These
informations are rarely used for composing HTML pages. Nevertheless, they are at your
disposal.
-
+
public type HTTP_Info:
- http_info
+ http_info
(
Word32 ip_address, // IP address of the client
String hostname, // hostname requested by the client
@@ -191,25 +191,25 @@ public type HTTP_Info:
Bool is_https
//One -> String generate_trust_ticket // may be used against denial of
// service attacks
- ).
+ ).
+
+
-
-
Each site is described by a 'web site description', which is a datum of type
'Web_Site_Description'.
-
+
public type Web_Site_Description:
web_site_description(
- List(String) common_names,
- String site_directory,
- Redirections redirections,
- String charset,
- List(String) journal_extensions,
- List(String) journal_headers,
- String authorization_secret,
- List(MIME) known_mime_types,
- (String host_name,
- HTTP_Info http_info,
+ List(String) common_names,
+ String site_directory,
+ Redirections redirections,
+ String charset,
+ List(String) journal_extensions,
+ List(String) journal_headers,
+ String authorization_secret,
+ List(MIME) known_mime_types,
+ (String host_name,
+ HTTP_Info http_info,
List(Web_arg) lwa,
Bool is_https) -> (//List(HTTP_header),
Printable_tree) awp_handler,
@@ -222,36 +222,36 @@ public type Web_Site_Description:
"www.our-business.com". The reason why we have a list of common names instead of a
single common name, is that it may be useful to have a common name like "192.168.0.1"
for testing.
-
+
'charset' is a string which will determine the character encoding to be used by the
browser. Typically, this string is one of: "UTF-8", "ISO-8859-1", "Windows-1252",
etc...
-
+
'journal_extensions' is the list of URI extensions for which you want a log in the
journal (and on the console). When a request arrives, and if the extension is a member
of this list, a message is printed into the journal of the site including the date, the
IP address of the client, the complete HTTP request line. The HTTP headers whose name
is a member of 'journal_headers' are also printed in the journal. A reasonable minimum
for these two components is:
-
+
[".awp"] for journal_extensions
["user-agent"] for journal_headers
-
+
'authorization_secret' is a string which should just be unguessable. You may choose
something like (but don't choose this one !):
-
+
"Hg8kJe42gCML9jNH-74"
-
+
i.e. a sequence of characters typed at random, long enough to be unguessable. This is
- used by the 'private download' mecanism, which is discussed later in this file.
-
+ used by the 'private download' mecanism, which is discussed later in this file.
+
The component 'awp_handler' is a function of type:
-
- (String host_name,
- HTTP_Info http_info,
+
+ (String host_name,
+ HTTP_Info http_info,
List(Web_arg) web_args,
Bool is_https) -> Printable_tree
-
+
('Printable_tree' is a substitute for 'String' and is defined in
'tools/basis.anubis'). This function is the 'awp handler' for the site. When the URI
ends by ".awp", this function is called, and the result (an HTML page) is sent to the
@@ -259,173 +259,173 @@ public type Web_Site_Description:
'true' when the requests arrives through the HTTPS channel, and 'false' when it arrives
through the HTTP channel.
-
-
-
-
-
-
- *** (5) Protection against denial of service attacks.
-
+
+
+
+
+
+
+ *** (5) Protection against denial of service attacks.
+
We need to protect our servers against 'denial of service' attacks. The attack may be
send automatically from machines which are infested by viruses. In that case, our
server is saturated of connections (all virtual machines at work), but nothing is
comming on the connections. In order to avoid this problem, we propose the following:
-
+
(1) Limit the number of simultaneous connections (say to 100).
(2) Close a connection if the request is not complete after say 10 seconds.
(3) Close the connection if the request is bigger than a given size (normal requests
- are small except when there are uploaded files.
+ are small except when there are uploaded files.
(4) Close the connection during the sending of the answer if the client is waiting
too much.
(5) Record all IP addresses with which we have encountered one of the problems above.
(6) Immediately close the connections if the IP address is in our list.
(7) Remove an address from the list only after 5 minutes of inactivity of this
address.
- (8) Maintain a list of reliable IP addresses.
+ (8) Maintain a list of reliable IP addresses.
Of course, all the above are approximative solutions which may in some circumstances
become either cumbersome or also partially block the system. So, it is needed to have a
set of dynamically modifiable parameters in order to master the behavior of this
mecanism.
-
-
+
+
Each dubious IP address is recorded together with its last activity time.
-
+
public type DubiousIP:
dubious_ip (Word32 address,
- Int last_activity).
-
-
+ Int last_activity).
+
+
public type DenialOfService:
- denial_of_service(Var(Int) max_connections,
+ denial_of_service(Var(Int) max_connections,
Var(Int) request_line_delay, // seconds
- Var(Int) headers_delay,
- Var(Int) answer_delay,
+ Var(Int) headers_delay,
+ Var(Int) answer_delay,
Var(List(DubiousIP)) list_of_dubious,
- Var(List(Word32)) reliable_addresses).
-
+ Var(List(Word32)) reliable_addresses).
+
The informations in this set of variables are stored serialized into the file
'my_anubis/web_sites/dos_info'. If this file does not exist a set if variables with
default values is created. The values are saved on the disk each time they are
modified.
-
-public define DenialOfService load_denial_of_service_info.
-
-
-
- *** (6) Starting your HTTP and HTTPS servers.
-
+
+public define DenialOfService load_denial_of_service_info.
+
+
+
+ *** (6) Starting your HTTP and HTTPS servers.
+
When your web site descriptions are ready, you can start a pair of servers (a HTTP
server and a HTTPS server) for serving your web sites. Notice that there are always
two servers, regardless of the number of web sites, and that each web sites normally
uses the two servers.
-
-
+
+
public define StartServerResult
start_http_server
(
- Word32 ip_address,
- Word32 http_port,
+ Word32 ip_address,
+ Word32 http_port,
List(Web_Site_Description) web_sites,
DenialOfService dos
- ).
-
+ ).
+
public define StartServerResult
start_https_server
(
- Word32 ip_address,
+ Word32 ip_address,
Word32 https_port,
- String certificate_common_name,
+ String certificate_common_name,
List(Web_Site_Description) web_sites,
DenialOfService dos
- ).
-
+ ).
+
The first argument 'ip_address' is the IP address on which the servers listen. If you
put 0, the servers listen on all adresses of the machine (which is useful if the
machine has several network interfaces). Otherwise, use the function 'ip_address'
- defined in 'tools/basis.anubis' for composing a particular IP address.
-
+ defined in 'tools/basis.anubis' for composing a particular IP address.
+
The next arguments are the port numbers for HTTP and HTTPS. The usual values are 80 and
- 443, but you may have reasons to choose other values.
-
+ 443, but you may have reasons to choose other values.
+
The next argument is the list of your web site descriptions. All the sites described in
this list will be accessible on the server.
-
+
The argument 'dos' is a set of dynamic variables containing the informations for
protecting the servers against denial of service attacks.
-
-
-
-
-
-
- *** (7) Private download.
-
+
+
+
+
+
+
+ *** (7) Private download.
+
It may happen that you want to propose private files for download. This means that such
a file could be downloaded only by the authorized person, and should not be seen by any
other one. This feature can be used only under HTTPS, not under HTTP.
The file may be located anywhere on the server. Hence, the file has a complete absolute
path, like for example:
-
+
/home/georges/my_documents/my_text.pdf
-
+
which has nothing to do with the directories of the web server. Now, you may also want
to show another path or simply just a name to the client, not the actual absolute path
above, which may need to remain secret. So for example, the same file may appear to the
client as:
-
+
informations.pdf
-
+
The page must provide a link with an authorization. The authorization is just a web
argument, whose name is "zauth". The value of this web argument is computed by hashing
some secret string (known only from the programmer of the web site) with the absolute
path of the file. The HTTPS request will have the form:
-
+
GET /informations.pdf?zauth=d38161f5b4e87e2d46e06ff8b3e233be563794d1
-
- The server will search for a file named
-
+
+ The server will search for a file named
+
zd38161f5b4e87e2d46e06ff8b3e233be563794d1
-
+
(i.e. "z" concatenated with the value of the authorization) in the subdirectory
'private_download' of the site directory. This file contains the absolute path of the
file, i.e:
-
+
/home/georges/my_documents/my_text.pdf
-
+
At that point, the server may hash the secret string and the absolute path together, to
check if the client is authorized to download the file. If it is the case, it sends the
file (the MIME type is declared as 'application/octet-stream' if it is not recognized).
The file is sent under the visible name.
-
+
The server creates automatically the subdirectory 'private_download/' within the 'site
directory' (for each web site) if it does not already exist. Files in this directory
are deleted when they become too old (for example, after 3 days of life).
-
+
Here is the function for computing the value of the authorization, and for making the
authorization file in 'private_download'.
-
-public define String
+
+public define String
make_authorization
(
- String site_directory,
+ String site_directory,
String authorization_secret, // known only by the programmer of the web site
String absolute_path // on server
- ).
-
- See 'web/making_a_web_site.anubis' for the construction of the link for downloading.
-
-
-
-
-
-
-
-
- *** (8) About web argument names.
-
+ ).
+
+ See 'web/making_a_web_site.anubis' for the construction of the link for downloading.
+
+
+
+
+
+
+
+
+ *** (8) About web argument names.
+
The server reserves the name "zauth" for the authorization in the private download
mecanism. Also, if the name of a web arguments begins by "p" (like 'password'), it does
not print the value of the web argument neither on the console or in the journal. A
@@ -433,18 +433,18 @@ public define String
method is used in 'web/making_a_web_site.anubis'. This will avoid clashes of names.
-
-
-
-
- *** (9) A web dispatcher.
-
+
+
+
+
+ *** (9) A web dispatcher.
+
For hosting several sites you may prefer another method which we now describe. We start
a HTTP server on port 80 (or on another port). This server is called the
``dispatcher''. When a requests arrives, the dispatcher examines the ``host'' HTTP
header, so that it gets the name of the requested host. Then it sends to the client a
page like this one:
-
+
@@ -452,53 +452,53 @@ public define String
-
+
where the URL represented by '...' is the URL of the requested site. This URL may have
the same IP address as the dispatcher, except that the port number is different. It may
also have a different IP address.
-
+
The dispatcher uses the file 'my_anubis/web_sites/dispatcher.info'. This file contains
- a serialized datum of type 'List(DispatcherInfo)'.
-
+ a serialized datum of type 'List(DispatcherInfo)'.
+
public type DispatcherInfo:
- site(String common_name,
- Word32 http_port).
-
+ site(String common_name,
+ Word32 http_port).
+
The dispatcher does not write into this file. It reads it when it starts, and rereads
it each time the date of last modification of the file changes, so that the dispatcher
always has up to date data. The file may be managed (written and updated) by another
program.
-
+
So, for each site, the dispatcher knows the common name (needed to recognize the 'host'
HTTP header), and the pair (ip_address,port) used by the actual site for HTTP. The
- dispatcher does not worry about HTTPS. HTTPS must be managed by the actual site.
-
- The dispatcher is started by:
-
+ dispatcher does not worry about HTTPS. HTTPS must be managed by the actual site.
+
+ The dispatcher is started by:
+
public define One
start_web_dispatcher
(
Word32 ip_address, // address for listening (typically 0)
Word32 port, // typically 80
DenialOfService dos
- ).
-
+ ).
+
A command line tool for managing the file 'my_anubis/web_sites/dispatcher.info' is also
provided:
-
+
global define One
manage_web_dispatcher
(
List(String) args
- ).
-
-
+ ).
+
+
*** (10) HTTP Errors
-
+
public type HTTP_Status:
http_continue, // 100
http_switching_protocol, // 101
-
+
http_ok, // 200
http_created, // 201
http_accepted, // 202
@@ -506,7 +506,7 @@ public type HTTP_Status:
http_no_content, // 204
http_reset_content, // 205
http_partial_content, // 206
-
+
http_multiple_choices,
http_moved_permanently(String location), // 301
http_moved_temporarily(String location), // 302
@@ -514,7 +514,7 @@ public type HTTP_Status:
http_not_modified, // 304
http_use_proxy(String location), // 305
http_temporary_redirect(String location), // 307
-
+
http_bad_request, // 400
http_unauthorized, // 401
http_payment_required, // 402
@@ -533,14 +533,14 @@ public type HTTP_Status:
http_unsupported_media_type, // 415
http_request_range_unsatisfiable, // 416
http_expectation_failed, // 417
-
+
http_internal_server_error, // 500
http_not_implemented, // 501
http_bad_gateway, // 502
http_service_unavailable, // 503
http_gateway_timeout, // 504
http_version_not_supported, // 505
-
+
http_error(Int /*code*/, String /*message*/).
public define (String, List(HTTP_header))
@@ -548,23 +548,23 @@ public define (String, List(HTTP_header))
(
HTTP_Status status
).
-
-
-
-
-
+
+
+
+
+
--- That's all for the public part ! --------------------------------------------------
-
+
define Maybe(String) get_host_header_value(List(HTTP_header) headers).
-
+
define String
__utime_to_string
(
UTime t
) =
to_decimal(t.seconds) + "." + zero_pad_n(6, t.microseconds ) + "s".
-
-
+
+
variable UTime t0 = utime(0,0).
variable UTime t1 = utime(0,0).
@@ -572,18 +572,18 @@ define String
accumulate_t1
(
UTime start
- ) =
+ ) =
with delta = (UTime)unow - start,
t1 <- delta + *t1;
unique.
-
+
variable UTime t2 = utime(0,0).
define One
accumulate_t2
(
UTime start
- ) =
+ ) =
with delta = (UTime)unow - start,
t2 <- delta + *t2;
unique.
@@ -593,208 +593,208 @@ define String
print_delta
(
String txt
- ) =
+ ) =
println(__utime_to_string((UTime)unow - *t0) + " : " + txt).
-
-
+
+
----------------------------------- Table of Contents ---------------------------------
-
- *** [1] Types which are private to this file.
-
- *** [2] Tools.
- *** [2.1] Formating an error message.
- *** [2.2] Converting IP addresses.
- *** [2.3] Reading and unputting characters.
- *** [2.4] Reading and discarding characters.
- *** [2.5] Reading a character string.
- *** [2.6] Padding integers with zeros.
- *** [2.7] Converting web arguments to ASCII.
- *** [2.8] Server description.
-
- *** [3] Managing the journal.
- *** [3.1] Naming journal files.
- *** [3.2] Formating HTTP headers.
- *** [3.3] Formating web arguments.
- *** [3.4] Formating the whole request.
- *** [3.5] Putting it in the journal file (and on the console).
-
- *** [4] Reading the HTTP request.
- *** [4.1] Skipping leading blanks.
- *** [4.2] Reading a new line.
- *** [4.3] Reading a 'word'.
- *** [4.4] Separating the URI from the query string.
- *** [4.5] Reading the web arguments.
- *** [4.7] Reading the request line.
- *** [4.8] Reading the HTTP headers.
- *** [4.9] Getting the size of the request's body.
+
+ *** [1] Types which are private to this file.
+
+ *** [2] Tools.
+ *** [2.1] Formating an error message.
+ *** [2.2] Converting IP addresses.
+ *** [2.3] Reading and unputting characters.
+ *** [2.4] Reading and discarding characters.
+ *** [2.5] Reading a character string.
+ *** [2.6] Padding integers with zeros.
+ *** [2.7] Converting web arguments to ASCII.
+ *** [2.8] Server description.
+
+ *** [3] Managing the journal.
+ *** [3.1] Naming journal files.
+ *** [3.2] Formating HTTP headers.
+ *** [3.3] Formating web arguments.
+ *** [3.4] Formating the whole request.
+ *** [3.5] Putting it in the journal file (and on the console).
+
+ *** [4] Reading the HTTP request.
+ *** [4.1] Skipping leading blanks.
+ *** [4.2] Reading a new line.
+ *** [4.3] Reading a 'word'.
+ *** [4.4] Separating the URI from the query string.
+ *** [4.5] Reading the web arguments.
+ *** [4.7] Reading the request line.
+ *** [4.8] Reading the HTTP headers.
+ *** [4.9] Getting the size of the request's body.
*** [4.10] Reading the body of the request.
-
- *** [5] Making the HTTP answer.
- *** [5.1] Avoiding illegal URIs.
- *** [5.2] Managing authorizations for downloading private files.
- *** [5.3] Recognizing MIME types.
- *** [5.4] Formating HTTP headers.
- *** [5.5] Sending a file.
- *** [5.6] Answering a www-url encoded request.
- *** [5.7] Answering a multipart/form-data encoded request.
+
+ *** [5] Making the HTTP answer.
+ *** [5.1] Avoiding illegal URIs.
+ *** [5.2] Managing authorizations for downloading private files.
+ *** [5.3] Recognizing MIME types.
+ *** [5.4] Formating HTTP headers.
+ *** [5.5] Sending a file.
+ *** [5.6] Answering a www-url encoded request.
+ *** [5.7] Answering a multipart/form-data encoded request.
*** [5.7.1] Finding the boundary.
- *** [5.7.2] Reading attributes from a multipart entity.
- *** [5.7.3] Creating a temporary filename for an uploaded file.
- *** [5.7.4] Saving an uploaded file under a temporary filename.
- *** [5.7.5] Removing the path from a file name.
- *** [5.7.6] Reading a multipart entity.
- *** [5.8] Handling redirections.
- *** [5.9] Answering both sorts of requests.
-
- *** [6] The HTTP/HTTPS servers.
- *** [6.1] The HTTP request handler.
- *** [6.2] Server's tasks.
- *** [6.3] Starting the HTTP/HTTPS servers.
-
- *** [7] The web dispatcher.
- *** [7.1] The dispatcher server.
- *** [7.2] The dispatcher web site.
- *** [7.3] Managing the info file.
-
+ *** [5.7.2] Reading attributes from a multipart entity.
+ *** [5.7.3] Creating a temporary filename for an uploaded file.
+ *** [5.7.4] Saving an uploaded file under a temporary filename.
+ *** [5.7.5] Removing the path from a file name.
+ *** [5.7.6] Reading a multipart entity.
+ *** [5.8] Handling redirections.
+ *** [5.9] Answering both sorts of requests.
+
+ *** [6] The HTTP/HTTPS servers.
+ *** [6.1] The HTTP request handler.
+ *** [6.2] Server's tasks.
+ *** [6.3] Starting the HTTP/HTTPS servers.
+
+ *** [7] The web dispatcher.
+ *** [7.1] The dispatcher server.
+ *** [7.2] The dispatcher web site.
+ *** [7.3] Managing the info file.
+
---------------------------------------------------------------------------------------
-
-
-
-
-read tools/basis.anubis
-read tools/findstring.anubis
-read tools/connections.anubis
-
-
-
-
-
- *** [1] Types which are private to this file.
-
- We use the following self-explanatory types.
-
+
+
+
+
+read tools/basis.anubis
+read tools/findstring.anubis
+read tools/connections.anubis
+
+
+
+
+
+ *** [1] Types which are private to this file.
+
+ We use the following self-explanatory types.
+
type Error:
cannot_read_from_connection,
not_get_or_post_request(String),
end_of_line_expected,
incorrect_content_length_value,
colon_expected,
- timeout(Int).
-
+ timeout(Int).
+
type HTTP_RequestType:
get,
- post.
-
+ post.
+
type HTTP_RequestLine:
- request_line (HTTP_RequestType type,
+ request_line (HTTP_RequestType type,
String uri,
- List(Web_arg) query_string).
-
+ List(Web_arg) query_string).
+
type EncodingType:
www_url,
- multipart_form_data.
-
+ multipart_form_data.
+
type BufferedConnection:
buffered_connection(Connection conn,
Var(ByteArray) buffer,
Var(Int) read_pos).
-
-
-
- *** [2] Tools.
-
- *** [2.1] Formating an error message.
-
- The next function formats an error message.
-
+
+
+
+ *** [2] Tools.
+
+ *** [2.1] Formating an error message.
+
+ The next function formats an error message.
+
define String
format
(
Error msg
) =
- if msg is
+ if msg is
{
- cannot_read_from_connection then
+ cannot_read_from_connection then
"Cannot read from connection.\n",
- not_get_or_post_request(s) then
+ not_get_or_post_request(s) then
"The request did not begin by 'GET' or 'POST': "+s+".\n",
- end_of_line_expected then
+ end_of_line_expected then
"End of line expected.\n",
- incorrect_content_length_value then
+ incorrect_content_length_value then
"Incorrect value for HTTP header 'Content-Length'.\n",
colon_expected then
- "':' was expected.\n",
- timeout(n) then
+ "':' was expected.\n",
+ timeout(n) then
//"time out: "+n+"\n"
//"time out.\n"
""
}.
-
-
-
-
-
-
- *** [2.2] Converting IP addresses.
-
+
+
+
+
+
+
+ *** [2.2] Converting IP addresses.
+
We need two conversion functions for IP addresses:
-
+
(Word8,Word8,Word8,Word8) --> Word32 ip_address
Word32 --> String ip_addr_to_string
-
- These conversions are defined in 'tools/basis.anubis'.
-
-
-
- *** [2.3] A set of state variables for the server.
-
+
+ These conversions are defined in 'tools/basis.anubis'.
+
+
+
+ *** [2.3] A set of state variables for the server.
+
public type SState:
sstate
(
Var(List(Word8)) unput_chars, // for reading requests
Var(Int) sttm, // 'start time'
- Var(Int) uploaded_file_count
- ).
-
-
- *** [2.4] Reading and unputting characters.
-
+ Var(Int) uploaded_file_count
+ ).
+
+
+ *** [2.4] Reading and unputting characters.
+
We need a mecanism for unputting several characters (actually at least 3). This is
because when reading the client connection, we must sometimes go ahead several
characters, and virtually put them back into the connection, so that they can be
reread. Of course, we do not send them back to the client. We store them in a list
(hold by the variable 'unput_chars'), and we manage this list, so that characters may
be virtually put back in the connection (this is called 'unputting').
-
- variable List(Word8) unput_chars = [].
-
+
+ variable List(Word8) unput_chars = [].
+
The most recently read one is the head of list. Fortunately, this variable is private
to this virtual machine (hence to this client).
-
-
+
+
define One
- unput // unputting a character (add it in front of the list)
+ unput // unputting a character (add it in front of the list)
(
Word8 character,
SState s
) =
- s.unput_chars <- (List(Word8))[character . *(s.unput_chars)].
-
-
-
-define One record_dubious_IP(Word32 addr,DenialOfService dos).
-
- variable Int sttm = 0. // contains the start time for this connection.
-
+ s.unput_chars <- (List(Word8))[character . *(s.unput_chars)].
+
+
+
+define One record_dubious_IP(Word32 addr,DenialOfService dos).
+
+ variable Int sttm = 0. // contains the start time for this connection.
+
define Result(Error,Word8)
record_dubious_connection
(
- Connection conn,
+ Connection conn,
Int dead_line,
DenialOfService dos,
SState s
) =
- if remote_IP_address_and_port(conn) is (addr,port) then
+ if remote_IP_address_and_port(conn) is (addr,port) then
record_dubious_IP(addr,dos);
print("Recording IP address "+ip_addr_to_string(addr)+
" as dubious after "+(dead_line-*(s.sttm))+" seconds. Total: "+
@@ -818,9 +818,9 @@ define One
if nth(i,source) is
{
failure then unique,
- success(b) then if put(dest,position,b) is
+ success(b) then if put(dest,position,b) is
{
- failure then unique,
+ failure then unique,
success(_) then put(source,dest,position+1,i+1)
}
}.
@@ -832,9 +832,9 @@ define ReadResult
Int size,
Int time_out,
ByteArray result_buffer,
- Int position
+ Int position
) =
- //println(pid + "read_from_connexion(" + size + ")");
+ //println(pid + "read_from_connexion(" + size + ")");
if *connection.read_pos < length(*connection.buffer) then
//println(pid + " reading from buffer (size = " + length(*connection.buffer) + ", pos = " + *connection.read_pos);
@@ -852,7 +852,7 @@ define ReadResult
// timeout then ok(result),
// ok(ba) then ok(result + ba)
// }
- else
+ else
ok(result_buffer)
else
with t0 = (UTime) unow,
@@ -861,24 +861,24 @@ define ReadResult
{
error then println(pid + "read failed ["+to_string(result_buffer)+"]"); error,
timeout then timeout,
- ok(ba) then
+ ok(ba) then
with size_read = length(ba),
- //the needed read is higher than the
+ //the needed read is higher than the
if size > size_read then
put(ba, result_buffer, position, 0);
- read_from_connexion(connection, size - size_read, time_out, result_buffer, position+size_read)
+ read_from_connexion(connection, size - size_read, time_out, result_buffer, position+size_read)
else
- //println(pid + "ba = " + length(ba) + " duration: " + __utime_to_string((UTime) unow - t0));
+ //println(pid + "ba = " + length(ba) + " duration: " + __utime_to_string((UTime) unow - t0));
connection.buffer <- ba;
connection.read_pos <- 0;
- //println(pid + "rb = " + length(*read_buffer));
-
+ //println(pid + "rb = " + length(*read_buffer));
+
terminal read_from_connexion(connection, size, time_out, result_buffer, position)
}.
define Result(Error,Word8)
next_char // reading a character (check the list first, and read on the connection
- // only when the list is empty).
+ // only when the list is empty).
(
BufferedConnection connection,
Int dead_line,
@@ -886,11 +886,11 @@ define Result(Error,Word8)
SState s
) =
//with t2_tmp = (UTime) now,
- if *(s.unput_chars) is
+ if *(s.unput_chars) is
{
- [ ] then
+ [ ] then
// ///////////////////
- // Buffered reading
+ // Buffered reading
//if unow > dead_line then record_dubious_connection(connection,dead_line,dos) else
if nth(*connection.read_pos, *connection.buffer) is
{
@@ -899,48 +899,48 @@ define Result(Error,Word8)
{
error then /*accumulate_t2(t2_tmp);*/ error(cannot_read_from_connection),
timeout then /*accumulate_t2(t2_tmp);*/ error(timeout(600)),
- //record_dubious_connection(connection,dead_line,dos),
- ok(ba) then if nth(0,ba) is
+ //record_dubious_connection(connection,dead_line,dos),
+ ok(ba) then if nth(0,ba) is
{
failure then /*accumulate_t2(t2_tmp);*/ error(cannot_read_from_connection),
- success(c) then
+ success(c) then
//println("-" + pid + "read [" + implode([c]) + "]\t");
- //accumulate_t2(t2_tmp);
+ //accumulate_t2(t2_tmp);
ok(c)
}
},
- success(c) then
+ success(c) then
connection.read_pos <- *connection.read_pos + 1;
- //accumulate_t2(t2_tmp);
+ //accumulate_t2(t2_tmp);
ok(c)
},
-
+
// ///////////////////
// standard reading
// if read(connection.conn, 1, 600) is // the connection is closed after 10 minutes of inactivity
// {
// error then accumulate_t2(t2_tmp); println(pid + "read failed)"); error(cannot_read_from_connection),
// timeout then accumulate_t2(t2_tmp); error(timeout(600)),
-// ok(ba) then if nth(0,ba) is
+// ok(ba) then if nth(0,ba) is
// {
// failure then accumulate_t2(t2_tmp); error(cannot_read_from_connection),
-// success(c) then accumulate_t2(t2_tmp);
+// success(c) then accumulate_t2(t2_tmp);
// ok(c)
// }
// },
-
- [h . t] then
- s.unput_chars <- t; //accumulate_t2(t2_tmp);
+
+ [h . t] then
+ s.unput_chars <- t; //accumulate_t2(t2_tmp);
ok(h)
- }.
-
+ }.
+
define ByteArray
get_and_erase_buffer
(
BufferedConnection connection,
SState s
)=
- with head = to_byte_array(implode(*s.unput_chars)),
+ with head = to_byte_array(implode(*s.unput_chars)),
tail = extract(*connection.buffer, *connection.read_pos, length(*connection.buffer)),
// println("--- get_and_erase_buffer ----");
// println("unput char length : "+length(to_string(head)));
@@ -949,105 +949,105 @@ define ByteArray
// println(" tail length : "+length(tail));
// println(" -- unputchar content ");
// println("["+to_string(head)+"]");
-// println(" -- buffer content ");
+// println(" -- buffer content ");
// println("["+to_string(*connection.buffer)+"]");
-// println(" -- tail content ");
+// println(" -- tail content ");
// println("["+to_string(tail)+"]");
s.unput_chars <- [];
connection.buffer <- constant_byte_array(0,0);
connection.read_pos <- 0;
head + tail
.
-
-
-
-
-
- *** [2.4] Reading and discarding characters.
-
+
+
+
+
+
+ *** [2.4] Reading and discarding characters.
+
The next function reads the specified number of bytes (this is the same as
'characters') from the connection and discards them. This is used for discarding CR LF
just before the body of a request.
-
+
define Result(Error,One)
read_and_ignore
(
BufferedConnection connection, // to client
- Int dead_line,
+ Int dead_line,
Int number_of_characters, // number of characters to read and ignore
DenialOfService dos,
- SState s
+ SState s
) =
if number_of_characters =< 0 then ok(unique) else
- if next_char(connection, dead_line, dos, s) is
+ if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
ok(c) then read_and_ignore(connection,dead_line,number_of_characters-1,dos, s)
}.
-
-
-
-
-
-
-
- *** [2.5] Reading a character string.
-
+
+
+
+
+
+
+
+ *** [2.5] Reading a character string.
+
Sometimes values of HTTP attributes or web args are presented in the form of double
quoted strings. The next function handles the reading of such things. The leading
double quote is already read in. We must read subsequent characters until the next non
backslashed double quote.
-
-define Result(Error,String)
+
+define Result(Error,String)
read_string
(
BufferedConnection connection, // connection with the client
- Int dead_line,
+ Int dead_line,
List(Word8) so_far, // characters read so far (in reverse order)
DenialOfService dos,
- SState s
+ SState s
) =
- if next_char(connection, dead_line,dos, s) is
+ if next_char(connection, dead_line,dos, s) is
{
error(msg) then error(msg),
- ok(c) then
+ ok(c) then
if c = '\\'
- then if next_char(connection,dead_line,dos, s) is
+ then if next_char(connection,dead_line,dos, s) is
{
- error(msg) then error(msg),
- ok(d) then
+ error(msg) then error(msg),
+ ok(d) then
if d = '\"'
- then read_string(connection,dead_line,['\"' . so_far],dos, s)
+ then read_string(connection,dead_line,['\"' . so_far],dos, s)
else read_string(connection,dead_line,[d, c . so_far],dos, s)
}
else if c = '\"'
then ok(implode(reverse(so_far)))
else read_string(connection,dead_line,[c . so_far],dos, s)
}.
-
-
-
-
-
-
-
-
-
-
-
-
- *** [2.7] Converting web arguments to ASCII.
+
+
+
+
+
+
+
+
+
+
+
+
+ *** [2.7] Converting web arguments to ASCII.
The function 'web_to_ascii' gets a character string and replaces web encoding by normal
ASCII encoding. This amounts to replacing:
-
+
+ by blank
%xx by the character whose ASCII code is xx in hexadecimal
-
- Note: We assume that '9' < 'A' (which is the case for ASCII code).
-
-
+ Note: We assume that '9' < 'A' (which is the case for ASCII code).
+
+
+
define Word8
web_decode
(
@@ -1056,10 +1056,10 @@ define Word8
) =
with n1 = if x1 +=< '9' then (x1 - '0') else if x1 +=< 'F' then (x1 - 'A' + 10) else (x1 - 'a' + 10),
n2 = if x2 +=< '9' then (x2 - '0') else if x2 +=< 'F' then (x2 - 'A' + 10) else (x2 - 'a' + 10),
- (n1 << 4) + n2.
-
+ (n1 << 4) + n2.
+
+
-
define String
web_to_ascii
(
@@ -1067,93 +1067,93 @@ define String
Int n, // current position in web_string
List(Word8) so_far
) =
- if nth(n,web_string) is
+ if nth(n,web_string) is
{
- failure then implode(reverse(so_far)),
- success(c) then
+ failure then implode(reverse(so_far)),
+ success(c) then
if c = '+'
then web_to_ascii(web_string,n+1,[' ' . so_far])
else if c = '%'
then if nth(n+1,web_string) is
{
failure then implode(reverse(so_far)),
- success(x1) then if nth(n+2,web_string) is
+ success(x1) then if nth(n+2,web_string) is
{
- failure then implode(reverse(so_far)),
+ failure then implode(reverse(so_far)),
success(x2) then web_to_ascii(web_string,n+3,[web_decode(x1,x2) . so_far])
}
}
else web_to_ascii(web_string,n+1,[c . so_far])
}.
-
-
-
-
-
-
-
-
- *** [3] Managing the journal.
-
+
+
+
+
+
+
+
+
+ *** [3] Managing the journal.
+
Concurrently working machines should not try to access the same file at the same
- time. This problem may be solved by using the 'protect' mecanism.
-
-
-
- *** [3.1] Naming journal files.
-
+ time. This problem may be solved by using the 'protect' mecanism.
+
+
+
+ *** [3.1] Naming journal files.
+
Since journal messages are rather prolific, we should have at least one file per
hour. Hence, the name of a journal file must be constructed from the current year,
month, day and hour. For example, it may be:
-
+
2003_03_12_19
-
- (this is for the journal of 7 PM to 8 PM, 2003/mar/12).
-
-define String
+
+ (this is for the journal of 7 PM to 8 PM, 2003/mar/12).
+
+define String
make_current_journal_file_name
=
- if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then
+ if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then
to_decimal(y)+"_"+
zero_pad_n(2,m)+"_"+
zero_pad_n(2,d)+"_"+
- zero_pad_n(2,h).
-
-
-
-
-
-
-
- *** [3.2] Formating HTTP headers.
-
+ zero_pad_n(2,h).
+
+
+
+
+
+
+
+ *** [3.2] Formating HTTP headers.
+
HTTP headers may be shown on the console or written in the journal. The function below
- formats a list of HTTP headers.
-
-define String
+ formats a list of HTTP headers.
+
+define String
show_format
(
- Web_Site_Description desc,
+ Web_Site_Description desc,
List(HTTP_header) headers,
) =
- if headers is
+ if headers is
{
[ ] then "",
- [h . t] then if h is http_header(name,value) then
+ [h . t] then if h is http_header(name,value) then
if member(journal_headers(desc),name)
then " | "+name+": "+value+"\n"+show_format(desc,t)
else show_format(desc,t)
}.
-
-
-
-
-
-
- *** [3.3] Formating web arguments.
-
- The same thing for web arguments.
-
+
+
+
+
+
+
+ *** [3.3] Formating web arguments.
+
+ The same thing for web arguments.
+
define String
show_format
(
@@ -1162,25 +1162,25 @@ define String
if lwa is
{
[ ] then "",
- [h . t] then if h is
+ [h . t] then if h is
{
- web_arg(n,v) then
+ web_arg(n,v) then
" | "+n+"="+(if nth(0,n) = success('p') then "" else v)+"\n"+show_format(t),
upload(n,fn,tfn) then
" | "+n+"="+fn+" (uploaded as '"+tfn+"')\n"+show_format(t)
}
}.
-
-
-
-
-
-
- *** [3.4] Formating the whole request.
+
+
+
+
+
+
+ *** [3.4] Formating the whole request.
It is cheap to transform month numbers into abbreviated month names. This enhances the
readability of the journal.
-
+
define String
format_month
(
@@ -1198,86 +1198,86 @@ define String
if m = 10 then "oct" else
if m = 11 then "nov" else
if m = 12 then "dec" else
- "???".
-
-
+ "???".
+
+
Below we format a whole HTTP request. This may give this (actually, it depends on how
you defined the values of 'journal_headers' and 'journal_extensions'):
-
+
[3] 2003/mar/10 10:06:57 from 123.456.123.456: /homepage.awp
| host: www.the-best-one.com
| user-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.0.1) Gecko/20020823 Netscape/7.0
The leading number between brackets is the number of the virtual machine which served
the URI.
-
+
define String
format_request
(
- Web_Site_Description desc,
- Connection client_connection,
- HTTP_RequestLine request_line,
- List(HTTP_header) headers,
+ Web_Site_Description desc,
+ Connection client_connection,
+ HTTP_RequestLine request_line,
+ List(HTTP_header) headers,
List(Web_arg) web_args
) =
with dt = convert_time(now),
- if remote_IP_address_and_port(client_connection) is (addr,port) then
+ if remote_IP_address_and_port(client_connection) is (addr,port) then
to_decimal(year(dt))+"/"+format_month(month(dt))+"/"+zero_pad_n(2,day(dt))+" "+
zero_pad_n(2,hour(dt))+":"+zero_pad_n(2,minute(dt))+":"+zero_pad_n(2,second(dt))+
" from "+ip_addr_to_string(addr)+
": "+uri(request_line)+"\n"+
show_format(desc,headers)+
- show_format(web_args).
+ show_format(web_args).
+
+
+
+
-
-
-
-
-
- *** [3.5] Putting it in the journal file (and on the console).
+
+ *** [3.5] Putting it in the journal file (and on the console).
We must not forget to 'protect' this operation, so that the messages of two machines
(working for the same site) will not be mixed together.
-
-define One
+
+define One
log_journal_msg
(
- Web_Site_Description desc,
+ Web_Site_Description desc,
String msg,
) =
- with ba_msg = to_byte_array("["+virtual_machine_id+"] "+msg+"\n"),
+ with ba_msg = to_byte_array("["+virtual_machine_id+"] "+msg+"\n"),
protect
(
if file(site_directory(desc)+"/journal/"+make_current_journal_file_name,append) is
{
failure then unique,
- success(journal_file) then
+ success(journal_file) then
forget(reliable_write(file(journal_file),ba_msg))
};
forget(reliable_write(file(stdout),ba_msg))
).
-
-
-
-
-
-
-
- *** [4] Reading the HTTP request.
-
-
- *** [4.1] Skipping leading blanks.
-
+
+
+
+
+
+
+
+ *** [4] Reading the HTTP request.
+
+
+ *** [4.1] Skipping leading blanks.
+
One of the peculiarities of HTTP is that the characters 13 (carriage return) and 10
(line feed) followed by either a space (32) or a tab (9), is considered as a blank not
containing any new line. 'skip_http_blanks' must skip all blanks characters until the
first non blank character, which should not be read in. Obviously, because of the above
peculiarity, we need at least 3 characters of lookahead to do this. In other words, we
must be able to unput at least 3 characters (hopefully we are).
-
- Strictly blanks characters are 'space' and 'tab'.
-
+
+ Strictly blanks characters are 'space' and 'tab'.
+
define Bool
is_strict_blank
(
@@ -1285,46 +1285,46 @@ define Bool
) =
if c = ' ' then true else c = '\t'.
-
+
On the contrary, blanks include 13 and 10.
-
+
define Bool
is_blank
(
Word8 c
) =
- if c = ' ' then true else
+ if c = ' ' then true else
if c = '\t' then true else
if c = 13 then true else
- c = 10.
+ c = 10.
+
+
+ Skipping HTTP blanks.
-
- Skipping HTTP blanks.
-
define Result(Error,One)
skip_http_blanks
(
BufferedConnection connection,
Int dead_line,
DenialOfService dos,
- SState s
- ) =
- if next_char(connection, dead_line, dos, s) is
+ SState s
+ ) =
+ if next_char(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(c) then
+ error(msg) then error(msg),
+ ok(c) then
if is_strict_blank(c)
then skip_http_blanks(connection, dead_line, dos, s)
else if c = 13
then if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg), // (unput(c); ok(unique)),
- ok(d) then
+ ok(d) then
if d = 10
- then if next_char(connection, dead_line, dos, s) is
+ then if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg), // (unput(d); unput(c); ok(unique)),
- ok(e) then
+ ok(e) then
if is_strict_blank(e)
then skip_http_blanks(connection, dead_line, dos, s)
else (unput(e, s); unput(d, s); unput(c, s); ok(unique))
@@ -1333,16 +1333,16 @@ define Result(Error,One)
}
else (unput(c, s); ok(unique))
}.
-
-
-
-
-
-
-
-
- *** [4.2] Reading a new line.
-
+
+
+
+
+
+
+
+
+ *** [4.2] Reading a new line.
+
Normally in HTTP a new line is the sequence 13 10 (carriage return line feed), not
followed by a space or tabulator. If it is followed by a space or tabulator, the three
characters are considered blanks, and no new line has been read. Before trying to read
@@ -1350,54 +1350,54 @@ define Result(Error,One)
we read another character. if this character is space or tab, we consider we have read
only blanks and we continue reading in order to find our new line. Otherwise, we unput
this character (which may be for example the first character of the name of the next
- header), and answer that we have seen a new line.
-
+ header), and answer that we have seen a new line.
+
Warning: we must not use this function for reading the last pair (13,10) before the
beginning of the body, because if the body is empty, there is no character to read
after this pair, so that the server could wait for a character which will never
come. This is the reason for 'read_and_ignore' above, which is used precisely for
reading that last (13,10) pair.
-
+
define Result(Error,One)
read_new_line
(
BufferedConnection connection,
Int dead_line,
DenialOfService dos,
- SState s
+ SState s
) =
- if skip_http_blanks(connection, dead_line, dos, s) is
+ if skip_http_blanks(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
- ok(_) then
- if next_char(connection, dead_line, dos, s) is
+ ok(_) then
+ if next_char(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
+ error(msg) then error(msg),
ok(c) then
if c = 13
- then if next_char(connection, dead_line, dos, s) is
+ then if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
- ok(d) then
+ ok(d) then
if d = 10
then ok(unique)
- else (unput(d, s);
- unput(c, s);
+ else (unput(d, s);
+ unput(c, s);
error(end_of_line_expected))
}
- else (unput(c, s);
+ else (unput(c, s);
error(end_of_line_expected))
}}.
-
-
-
-
-
-
-
-
- *** [4.3] Reading a 'word'.
-
+
+
+
+
+
+
+
+
+ *** [4.3] Reading a 'word'.
+
A 'word' is a sequence of characters which begins either by a double quote or not by a
double quote. (However, any leading blanks are read in and ignored. This is
accomplished by 'skip_http_blanks'.) If it begins by a double quote, it is read like a
@@ -1407,26 +1407,26 @@ define Result(Error,One)
double quoted, the right delimiting blank character is not read in (it is 'unput' back
into the connection), and may be read in again. This is needed because carriage return
or line feed which are 'blank', also have a meaning in HTTP.
-
+
define Result(Error,String)
read_word_aux
(
- BufferedConnection connection,
- Int dead_line,
+ BufferedConnection connection,
+ Int dead_line,
List(Word8) so_far,
DenialOfService dos,
- SState s
+ SState s
) =
- if next_char(connection,dead_line,dos, s) is
+ if next_char(connection,dead_line,dos, s) is
{
error(msg) then error(msg),
- ok(c) then
+ ok(c) then
if is_blank(c)
- then (unput(c, s);
+ then (unput(c, s);
ok(implode(reverse(so_far))))
else read_word_aux(connection,dead_line,[c . so_far],dos, s)
}.
-
+
define Result(Error,String)
read_word
(
@@ -1435,117 +1435,117 @@ define Result(Error,String)
DenialOfService dos,
SState s
) =
- if skip_http_blanks(connection,dead_line,dos, s) is
+ if skip_http_blanks(connection,dead_line,dos, s) is
{
- error(msg) then error(msg),
- ok(_) then
- if next_char(connection, dead_line, dos, s) is
+ error(msg) then error(msg),
+ ok(_) then
+ if next_char(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(c) then
+ error(msg) then error(msg),
+ ok(c) then
if c = '\"'
then read_string(connection,dead_line,[],dos, s)
else read_word_aux(connection,dead_line,[c],dos, s)
}
- }.
-
-
-
-
-
-
-
-
- *** [4.4] Separating the URI from the query string.
-
+ }.
+
+
+
+
+
+
+
+
+ *** [4.4] Separating the URI from the query string.
+
A 'query string' may be postfixed to the URI, just after a question mark. For example,
the client may send the following request:
-
+
GET /catalog.awp?item=3&color=blue
-
+
We separate this into an URI: "/catalog.awp" and the string: "item=3&color=blue" which
will be later transformed into the list:
-
+
[web_arg("item","3"),web_arg("color","blue")]
-
-
+
+
define (String,String)
separate_uri_from_query_string
(
String uri_and_query_string,
Int n
) =
- if nth(n,uri_and_query_string) is
+ if nth(n,uri_and_query_string) is
{
failure then (uri_and_query_string,""),
- success(c) then
+ success(c) then
if c = '?'
then (substr(uri_and_query_string,0,n),
substr(uri_and_query_string,n+1,length(uri_and_query_string)-(n+1)))
else separate_uri_from_query_string(uri_and_query_string,n+1)
}.
-
-
-
-
-
-
-
-
-
- *** [4.5] Reading the web arguments.
-
- HTTP/HTTPS requests are sent in one of two formats:
-
+
+
+
+
+
+
+
+
+
+ *** [4.5] Reading the web arguments.
+
+ HTTP/HTTPS requests are sent in one of two formats:
+
(1) www-url encoded
(2) multipart/form-data encoded
-
+
The first one is the normal (historical) way of encoding. The second one is required
for uploading files. A server which is supposed to accept upload of files must handle
both formats. The first thing to do is to decide the format of the request. This is
easily done by examining the HTTP headers. If we find the header:
-
+
Content-Type: multipart/form-data
-
+
the request is multipart/form-data encoded. Otherwise, it is 'www-url' encoded. We
first consider 'www-url' encoded requests.
-
+
For a 'www-url' encoded request, the web argument are either in the query string or in
the body of the request, or both. The format is the same for both:
-
+
name=value&name=value&...
-
+
However, we may also have
-
+
name
name=
name=&...
name&...
-
- i.e. some parts may be missing. Hence, we must be careful.
-
+
+ i.e. some parts may be missing. Hence, we must be careful.
+
Furthermore, web arguments must be translated from web to ASCII when www-url encoded.
-
+
define Bool
is_ampersand_or_equal
(
Word8 c
) =
- if c = '&' then true else c = '='.
-
-
-
+ if c = '&' then true else c = '='.
+
+
+
The function 'read_name_or_value' reads the string 's' starting at position 'n' until
- either the end of the string or the first '&' or '='.
-
-define String
+ either the end of the string or the first '&' or '='.
+
+define String
read_name_or_value
(
String s,
- Int start,
+ Int start,
Int i
) =
- if nth(i,s) is
+ if nth(i,s) is
{
failure then substr(s,start,i - start),
success(c) then
@@ -1553,101 +1553,101 @@ define String
then substr(s,start,i-start) // the separator is not included
else read_name_or_value(s,start,i+1)
}.
-
-
+
+
define List(Web_arg)
read_www_url_encoded_web_args
(
String s,
- Int start,
+ Int start,
) =
- with first = read_name_or_value(s,start,start),
+ with first = read_name_or_value(s,start,start),
if first = ""
- then []
- else with i = start+length(first),
- if nth(i,s) is
+ then []
+ else with i = start+length(first),
+ if nth(i,s) is
{
failure then [web_arg(first,"")],
success(c) then
if c = '&'
then [web_arg(first,"") . read_www_url_encoded_web_args(s,i+1)]
- else if c = '='
+ else if c = '='
then with second1 = read_name_or_value(s,i+1,i+1),
- // print("\""+second1+"\"\n");
+ // print("\""+second1+"\"\n");
with second = web_to_ascii(second1,0,[]),
[web_arg(first,second) . read_www_url_encoded_web_args(s,i+length(second1)+2)]
else print("**** ALERT **** badly formatted argument [" + s + "]!!!\n");
[]
}.
-
-
-
-
-
- *** [4.7] Reading the request line.
-
+
+
+
+
+
+ *** [4.7] Reading the request line.
+
'read_request_line' reads three words and a new line from the connection. It tries to
recognize "get" or "post" in the first word, separates the URI from the query string in
the second word, transforms the query string into a list of 'Web_arg', and finally
returns a datum of type 'HTTP_RequestLine' if no error arose.
-
-
+
+
define Result(Error,HTTP_RequestType)
identify_get_or_post
(
String s
- ) =
+ ) =
with ls = to_lower(s),
if ls = "get" then ok(get) else
if ls = "post" then ok(post) else
- error(not_get_or_post_request(ls)).
-
-define Result(Error,HTTP_RequestLine)
+ error(not_get_or_post_request(ls)).
+
+define Result(Error,HTTP_RequestLine)
read_request_line
(
BufferedConnection connection,
Int dead_line,
DenialOfService dos,
- SState s
+ SState s
) =
- if read_word(connection, dead_line, dos, s) is
+ if read_word(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(get_or_post) then if read_word(connection, dead_line, dos, s) is
+ error(msg) then error(msg),
+ ok(get_or_post) then if read_word(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(uri_and_query_string) then if read_word(connection, dead_line, dos, s) is
+ error(msg) then error(msg),
+ ok(uri_and_query_string) then if read_word(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(http_version) then if read_new_line(connection, dead_line, dos, s) is
+ error(msg) then error(msg),
+ ok(http_version) then if read_new_line(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
- ok(_) then if separate_uri_from_query_string(uri_and_query_string,0) is
- (uri,query_string) then if identify_get_or_post(get_or_post) is
+ ok(_) then if separate_uri_from_query_string(uri_and_query_string,0) is
+ (uri,query_string) then if identify_get_or_post(get_or_post) is
{
error(msg) then error(msg),
- ok(request_type) then
+ ok(request_type) then
ok(request_line(request_type, web_to_ascii(uri, 0, []), read_www_url_encoded_web_args(query_string,0)))
}
- }
+ }
}
}
}.
-
-
-
-
-
-
-
- *** [4.8] Reading the HTTP headers.
-
+
+
+
+
+
+
+
+ *** [4.8] Reading the HTTP headers.
+
Each header is made of a name (containing only letters, the underscore, digits and the
minus sign), a colon, a value, and a new line. The first empty line ends the headers.
-
- The next function tests characters acceptable in a header name.
-
+
+ The next function tests characters acceptable in a header name.
+
define Bool
is_header_name_char
(
@@ -1657,26 +1657,26 @@ define Bool
if ('A' +=< c & c +=< 'Z') then true else
if ('0' +=< c & c +=< '9') then true else
if c = '-' then true else
- c = '_'.
-
+ c = '_'.
+
define Result(Error,String)
read_header_name
(
BufferedConnection connection,
- Int dead_line,
+ Int dead_line,
List(Word8) so_far,
DenialOfService dos,
- SState s
+ SState s
) =
- if next_char(connection, dead_line, dos, s) is
+ if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
- ok(c) then
+ ok(c) then
if is_header_name_char(c)
then read_header_name(connection, dead_line, [to_lower(c) . so_far], dos, s)
else unput(c, s); ok(implode(reverse(so_far)))
- }.
-
+ }.
+
define Result(Error,One)
skip_colon
(
@@ -1686,43 +1686,43 @@ define Result(Error,One)
SState s
) =
//Skip the blank char until ':'
- if skip_http_blanks(connection, dead_line, dos, s) is
+ if skip_http_blanks(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(_) then
+ error(msg) then error(msg),
+ ok(_) then
if next_char(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(c) then
+ error(msg) then error(msg),
+ ok(c) then
if c = ':'
then ok(unique)
else error(colon_expected)
}}.
-
-
+
+
define Result(Error,String)
read_header_value
(
BufferedConnection connection,
- Int dead_line,
+ Int dead_line,
List(Word8) so_far,
DenialOfService dos,
SState s
) =
- if next_char(connection, dead_line, dos, s) is
+ if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
- ok(c) then
+ ok(c) then
if c = 13
- then if next_char(connection, dead_line, dos, s) is
+ then if next_char(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
ok(d) then
if d = 10
- then if next_char(connection, dead_line, dos, s) is
+ then if next_char(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(e) then
+ error(msg) then error(msg),
+ ok(e) then
if is_strict_blank(e)
then read_header_value(connection,dead_line, [e . so_far], dos, s)
else (unput(e, s); ok(implode(reverse(so_far))))
@@ -1731,106 +1731,106 @@ define Result(Error,String)
}
else read_header_value(connection,dead_line,[c . so_far],dos, s)
}.
-
-
+
+
Reading a single header.
-
+
define Result(Error,Maybe(HTTP_header))
read_header
(
BufferedConnection connection,
Int dead_line,
DenialOfService dos,
- SState s
+ SState s
) =
//Find the name
- if read_header_name(connection, dead_line, [], dos, s) is
+ if read_header_name(connection, dead_line, [], dos, s) is
{
- error(msg) then error(msg),
- ok(name) then
- if name = "" then
+ error(msg) then error(msg),
+ ok(name) then
+ if name = "" then
if read_and_ignore(connection, dead_line, 2, dos, s) /* 13 and 10 */ is
{
- error(msg) then error(msg),
+ error(msg) then error(msg),
ok(_) then // this is the blank line
ok(failure) // end of headers
}
//skip the ':' and blank before and after it
- else if skip_colon(connection, dead_line, dos, s) is
+ else if skip_colon(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
- ok(_) then
+ error(msg) then error(msg),
+ ok(_) then
//skip the blank char after the ':'
if skip_http_blanks(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
- ok(_) then
+ ok(_) then
//Now read the value
if read_header_value(connection, dead_line, [], dos, s) is
{
- error(msg) then error(msg),
+ error(msg) then error(msg),
ok(value) then ok(success(http_header(name,value)))
}
}
}
}.
-
-
- Reading all the headers.
-
+
+
+ Reading all the headers.
+
define Result(Error,List(HTTP_header))
read_http_headers
(
- BufferedConnection connection,
+ BufferedConnection connection,
Int dead_line,
DenialOfService dos,
- SState s
+ SState s
) =
- if read_header(connection, dead_line, dos, s) is
+ if read_header(connection, dead_line, dos, s) is
{
- error(msg) then error(msg),
+ error(msg) then error(msg),
ok(mbh) then if mbh is
{
- failure then ok([ ]),
- success(header) then
- if read_http_headers(connection, dead_line, dos, s) is
+ failure then ok([ ]),
+ success(header) then
+ if read_http_headers(connection, dead_line, dos, s) is
{
error(msg) then error(msg),
ok(others) then ok([header . others])
}
}
}.
-
-
-
-
-
-
-
- *** [4.9] Getting the size of the request's body.
+
+
+
+
+
+
+
+ *** [4.9] Getting the size of the request's body.
The size of the body of the request is given under the 'Content-Length' header. If this
- header is not present, the size is assumed to be zero.
-
-define Result(Error,Int)
+ header is not present, the size is assumed to be zero.
+
+define Result(Error,Int)
get_body_size
(
List(HTTP_header) headers
- ) =
- if headers is
+ ) =
+ if headers is
{
- [ ] then ok(0),
- [h . t] then if h is http_header(name,value) then
+ [ ] then ok(0),
+ [h . t] then if h is http_header(name,value) then
if name = "content-length" then
if decimal_scan(value) is
{
- failure then error(incorrect_content_length_value),
+ failure then error(incorrect_content_length_value),
success(n) then ok(n)
}
else get_body_size(t)
}.
-
+
*** [4.10] Reading the body of the request.
@@ -1842,11 +1842,11 @@ define Result(Error,Int)
required number of bytes. However, if the number of bytes read is zero, the connection
may be broken. In that case, we must not try to read indefinitely. On the contrary, we
make at most 10 retries, with a small sleeping time between any two of them.
-
+
define Result(Error, ByteArray)
read_http_body
(
- BufferedConnection connection,
+ BufferedConnection connection,
Int body_size,
ByteArray so_far, // when calling this function, 'so_far' is the empty byte array
Int retries // this function is called with retries = 10
@@ -1854,7 +1854,7 @@ define Result(Error, ByteArray)
if body_size = 0 then ok(constant_byte_array(0,0)) else
if retries =< 0 then error(cannot_read_from_connection) else
- if read_from_connexion(connection,body_size,60,constant_byte_array(body_size,0),0) is
+ if read_from_connexion(connection,body_size,60,constant_byte_array(body_size,0),0) is
{
error then error(cannot_read_from_connection),
timeout then error(timeout(60)),
@@ -1864,54 +1864,54 @@ define Result(Error, ByteArray)
nn = length(new_bytes), // number of bytes just read
if nr < body_size // must read more bytes
then if nn > 0 // if connection seems to work
-
- then
+
+ then
println("http body ("+body_size+") read "+nn+" bytes current size "+nr+" Bytes");
read_http_body(connection,body_size,ba,1000) // continue reading
else sleep(100); // otherwise, sleep 1/10 of second
read_http_body(connection,body_size,ba, // and retry reading
retries-1) // but no more than 10 times
- else ok(ba) // required number of bytes has been read
- }.
-
-
+ else ok(ba) // required number of bytes has been read
+ }.
+
+
Note: During sleeping, 'anbexec' runs other machines. Actually, calling 'sleep', even
for one millisecond, is some way of giving up explicitly, so that other virtual
machines may work.
-
-
-
-
-
-
-
-
-
-
-
-
- *** [5] Making the HTTP answer.
-
+
+
+
+
+
+
+
+
+
+
+
+
+ *** [5] Making the HTTP answer.
+
At that point we have read the request line, the headers and the body of the
request, and we must decide what to do.
-
+
Actually, we can do one of the following:
-
+
- send a file,
- - execute 'tickets_and_web_page' in case of an ".awp" URI.
-
+ - execute 'tickets_and_web_page' in case of an ".awp" URI.
+
The uploaded file (which are in the body of the request) are saved into temporary files
below.
-
-
-
-
- *** [5.1] Avoiding illegal URIs.
-
+
+
+
+
+ *** [5.1] Avoiding illegal URIs.
+
For security reasons, we must avoid illegal URIs, for example those which may climb up
in the file hierarchy. First we accept only few characters in URIs.
-
+
define Bool
is_legal_uri_char
(
@@ -1921,28 +1921,28 @@ define Bool
if ('A' +=< c & c +=< 'Z') then true else // accept 'A' to 'Z'
if ('0' +=< c & c +=< '9') then true else // accept '0' to '9'
if c = '.' then true else // accept '.' '-' '/' and '_'
- if c = '-' then true else
+ if c = '-' then true else
if c = '/' then true else
- c = '_'.
-
+ c = '_'.
+
We do not accept ~ which is some way of climbing. Of course, we cannot disallow single
dots, which are most often present in legal URIs, but we must avoid double dots ..
which mean 'climb up'.
-
+
define Bool
is_illegal_uri
(
String uri,
Int n
) =
- if nth(n,uri) is
+ if nth(n,uri) is
{
- failure then false,
+ failure then false,
success(c) then
if c = '.' // first dot
- then if nth(n+1,uri) is
+ then if nth(n+1,uri) is
{
- failure then false,
+ failure then false,
success(d) then
if d = '.' // second dot
then true
@@ -1950,79 +1950,79 @@ define Bool
}
else is_illegal_uri(uri,n+1)
}.
-
-
-
-
-
-
- *** [5.2] Managing authorizations for downloading private files.
+
+
+
+
+
+
+ *** [5.2] Managing authorizations for downloading private files.
Computing the authorization and making the authorization file (containing the absolute
path of the file on the server).
-
-
-define String
- compute_authorization
+
+
+define String
+ compute_authorization
(
- String authorization_secret,
+ String authorization_secret,
String absolute_path
) =
to_ascii(sha1((authorization_secret,
- absolute_path))).
-
-
-public define String
+ absolute_path))).
+
+
+public define String
make_authorization
(
String site_directory,
- String authorization_secret,
+ String authorization_secret,
String absolute_path
) =
- with private_download_dir = site_directory+"/private_download",
+ with private_download_dir = site_directory+"/private_download",
auth = compute_authorization(authorization_secret,
- absolute_path),
+ absolute_path),
forget(save(absolute_path,
- private_download_dir+"/z"+auth));
- auth.
-
-
- The function 'send_file' defined below handles the recognition of authorizations.
-
-
-
-
-
- *** [5.3] Recognizing MIME types.
-
+ private_download_dir+"/z"+auth));
+ auth.
+
+
+ The function 'send_file' defined below handles the recognition of authorizations.
+
+
+
+
+
+ *** [5.3] Recognizing MIME types.
+
The extension of the (redirected) URI must be either ".awp" or recognized as associated
to a MIME type. Otherwise, the server will not send the file. This is for security, but
also because, we must generate a 'Content-Type' header in the answer, with the right
MIME type.
-
+
define String
get_uri_extension_aux
(
String uri,
Int n // used for searching backwards
) =
- if nth(n,uri) is
+ if nth(n,uri) is
{
- failure then "",
- success(c) then
+ failure then "",
+ success(c) then
if c = '.' then substr(uri,n,length(uri)-n)
else if c = '/' then ""
else get_uri_extension_aux(uri,n-1)
}.
-
+
public define String
get_uri_extension
(
String uri
) =
get_uri_extension_aux(uri,
- length(uri)-1). // search starts at the right end
-
+ length(uri)-1). // search starts at the right end
+
public define Bool
contains_no_case
(
@@ -2032,42 +2032,42 @@ public define Bool
if l is
{
[] then false,
- [h . t] then
+ [h . t] then
if insensitive_equal(h, val) then true
else contains_no_case(t, val)
}.
-
-define Maybe(MIME)
+
+define Maybe(MIME)
recognize_mime_type_from_ext
(
String ext,
List(MIME) l
) =
- if l is
+ if l is
{
- [ ] then success(mime("application", "octet-stream", [])), // failure,
+ [ ] then success(mime("application", "octet-stream", [])), // failure,
[h . t] then if h is mime(type, subtype, extensions) then
if contains_no_case(extensions, ext)
then success(h)
else recognize_mime_type_from_ext(ext,t)
}.
-
+
define Maybe(MIME)
recognize_mime_type_from_uri
(
- Web_Site_Description desc,
+ Web_Site_Description desc,
String uri
) =
recognize_mime_type_from_ext(get_uri_extension(uri),known_mime_types(desc)).
-
-
- *** [5.4] Formating HTTP headers.
-
+
+
+ *** [5.4] Formating HTTP headers.
+
This is the formating for sending to the client (hence, it has nothing to do with the
component 'journal_headers' in the web site description).
-
+
public define Printable_tree
format_headers
(
@@ -2079,8 +2079,8 @@ public define Printable_tree
[h . t] then if h is http_header(name,value) then
[name,": ",value,crlf . format_headers(t)]
}.
-
-
+
+
define String
month_abrv
@@ -2099,7 +2099,7 @@ define String
else if d.month = 10 then "Oct"
else if d.month = 11 then "Nov"
else if d.month = 12 then "Dec"
- else
+ else
println("Bad month value [" + d.month + "] on Date_and_Time");
"XXX".
@@ -2115,10 +2115,10 @@ define String
else if d.week_day = 4 then "Thu"
else if d.week_day = 5 then "Fri"
else if d.week_day = 6 then "Sat"
- else
+ else
println("Bad weekday value [" + d.week_day + "] on Date_and_Time");
"XXX".
-
+
/**
* Format a date with the followin format : "Mon, 23 Jul 2007 11:33:43 GMT"
* Currently, this function can't output a GMT time, but only local time.
@@ -2129,7 +2129,7 @@ public define String
(
Date_and_Time d
) =
- weekday_abrv(d) + ", " + zero_pad_n(2,day(d)) + " " + month_abrv(d) + " " + year(d)
+ weekday_abrv(d) + ", " + zero_pad_n(2,day(d)) + " " + month_abrv(d) + " " + year(d)
+ " " + zero_pad_n(2,hour(d)) + ":" + zero_pad_n(2,minute(d)) + ":" + zero_pad_n(2,second(d)) + " GMT".
/**
@@ -2141,12 +2141,12 @@ public define String
Int date
) =
format_http_date(convert_time(date)).
-
- *** [5.5] Sending a file.
- We send 2 headers 'Content-Type' and 'Content-Length'.
-
+ *** [5.5] Sending a file.
+
+ We send 2 headers 'Content-Type' and 'Content-Length'.
+
define List(HTTP_header)
headers_for_send_file
(
@@ -2167,15 +2167,15 @@ define List(HTTP_header)
success(ftimes) then [http_header("Last-Modified", format_http_date(to_Int(ftimes.last_modified))) . headers]
}
.
-
-
-
- Sending the body of the answer (i.e. the file itself).
-
+
+
+
+ Sending the body of the answer (i.e. the file itself).
+
define One
send_file_body
(
- Web_Site_Description desc,
+ Web_Site_Description desc,
Connection connection, // connection with the client
Connection file, // file to be sent already opened
Int size, // size of file
@@ -2183,21 +2183,21 @@ define One
String filename // name of file
) =
if sent >= size then unique else
- if read(file,min(16384,size-sent),60) is
+ if read(file,min(16384,size-sent),60) is
{
error then log_journal_msg(desc,"Cannot read from file '"+filename+"'.\n"),
timeout then log_journal_msg(desc,"Cannot read from file timeoput'"+filename+"'.\n"),
- ok(ba) then
+ ok(ba) then
with nr = length(ba), // get the number of bytes read
- if reliable_write(connection, ba) is
+ if reliable_write(connection, ba) is
{
- failure then log_journal_msg(desc,"Cannot write into connection delirering '"+filename+"' (sent="+sent+"; size="+size+"; current="+nr+").\n"),
- success(nw) then
- send_file_body(desc,connection,file,size,sent+nw,filename)
+ failure then log_journal_msg(desc,"Cannot write into connection delirering '"+filename+"' (sent="+sent+"; size="+size+"; current="+nr+").\n"),
+ success(nw) then
+ send_file_body(desc,connection,file,size,sent+nw,filename)
}
- }.
+ }.
+
-
define String
compute_etag
(
@@ -2205,7 +2205,7 @@ define String
Maybe(FileTimes) mb_ftimes,
Int size,
) =
- if mb_ftimes is
+ if mb_ftimes is
{
failure then println("Warning: no file times for '" + filename + "', etag won't be very accurate."); to_ascii(sha1((filename, size))),
success(ftimes) then to_ascii(md5((filename, ftimes, size)))
@@ -2222,13 +2222,13 @@ define Bool
failure then false,
success(etag) then trim_token(etag, '\"') = current_etag //remove triming " because this is quoted string
}.
-
- Sending the answer line, the headers and the body.
-define One
+ Sending the answer line, the headers and the body.
+
+define One
send_file
(
- Web_Site_Description desc,
+ Web_Site_Description desc,
Connection connection,
List(HTTP_header) input_headers,
List(HTTP_header) headers,
@@ -2239,79 +2239,79 @@ define One
MIME mime_type,
One -> One action_before_send_file
) =
- action_before_send_file(unique);
- with input_etag = http_header_value(input_headers, "If-None-Match"),
+ action_before_send_file(unique);
+ with input_etag = http_header_value(input_headers, "If-None-Match"),
mb_ftimes = get_file_times(full_path),
current_etag = compute_etag(full_path, mb_ftimes, size),
if are_same_etag(input_etag, current_etag) is
{
- false then
- forget(reliable_write(connection,to_byte_array("HTTP/1.1 200 OK"+crlf)));
+ false then
+ forget(reliable_write(connection,to_byte_array("HTTP/1.1 200 OK"+crlf)));
forget(reliable_write(connection,[format_headers(headers + headers_for_send_file(mime_type, size, current_etag, mb_ftimes)) , crlf]));
//forget(copy_file_to_Connection(file, connection, size)),
send_file_body(desc,connection,file,size,0,filename),
true then
- forget(reliable_write(connection,to_byte_array("HTTP/1.1 304 Not Modified"+crlf)));
+ forget(reliable_write(connection,to_byte_array("HTTP/1.1 304 Not Modified"+crlf)));
forget(reliable_write(connection,[format_headers([http_header("Content-length","0") . [http_header("Etag", "\""+current_etag+"\"") . headers]]) , crlf]))
//send_file_body(desc,connection,file,size,0,filename)
- }.
-
+ }.
+
- Checking if a connection is under SSL.
-
+ Checking if a connection is under SSL.
+
define Bool
is_SSL
(
Connection c
) =
- if c is
+ if c is
{
- file_r(_) then false,
- file_w(_) then false,
- file_rw(_) then false,
- tcp(_) then false,
+ file_r(_) then false,
+ file_w(_) then false,
+ file_rw(_) then false,
+ tcp(_) then false,
ssl(_) then true
- }.
+ }.
+
+
-
-
Before opening and sending a file, we check the MIME type. It must be recognized,
except if there is a valid authorization for private download.
define One
send_file
(
- Web_Site_Description desc,
- Connection connection,
- String uri,
+ Web_Site_Description desc,
+ Connection connection,
+ String uri,
List(HTTP_header) input_headers,
List(HTTP_header) output_headers,
Maybe(String) mbauthorization,
One -> One action_before_send_file
) =
- if mbauthorization is
+ if mbauthorization is
{
//--- file without authorization: take it from public ---
- failure then if recognize_mime_type_from_uri(desc,uri) is
+ failure then if recognize_mime_type_from_uri(desc,uri) is
{
failure then log_journal_msg(desc,"No MIME type found for '"+uri+"'.\n"),
- success(mime_type) then
+ success(mime_type) then
with path = site_directory(desc)+"/public"+uri,
- if (Maybe(RStream))file(path, read) is
+ if (Maybe(RStream))file(path, read) is
{
- failure then
+ failure then
println("HTTP/1.1 404 Not Found"+path);
forget(reliable_write(connection,to_byte_array("HTTP/1.1 404 Not Found"+crlf+
"Content-Length: 0"+crlf+crlf
/*+"Connection: close"+crlf+crlf*/)));
log_journal_msg(desc,"Cannot find file '"+path+"'.\n"),
- success(f) then with size = file_size(f),
+ success(f) then with size = file_size(f),
send_file(desc,
connection,
input_headers,
output_headers,
- size,
+ size,
file(f),
uri,
path,
@@ -2319,60 +2319,60 @@ define One
action_before_send_file)
}
},
-
+
//--- file with authorization: apply 'private download' mecanism ---
- success(authorization) then
- with private_download_dir = site_directory(desc)+"/private_download",
- if (RetrieveResult(String))retrieve(private_download_dir+"/z"+authorization)
- is ok(absolute_path)
+ success(authorization) then
+ with private_download_dir = site_directory(desc)+"/private_download",
+ if (RetrieveResult(String))retrieve(private_download_dir+"/z"+authorization)
+ is ok(absolute_path)
then (
with new_hash = compute_authorization(authorization_secret(desc),
- absolute_path),
- if (Maybe(RStream))file(absolute_path, read) is
+ absolute_path),
+ if (Maybe(RStream))file(absolute_path, read) is
{
- failure then
+ failure then
println("HTTP/1.1 404 Not Found"+absolute_path);
forget(reliable_write(connection,to_byte_array("HTTP/1.1 404 Not Found"+crlf+
"Content-Length: 0"+crlf+crlf
/*+"Connection: close"+crlf+crlf*/)));
log_journal_msg(desc,"Cannot find file '"+absolute_path+"'.\n"),
- success(f) then with size = file_size(f),
- mime_type = if recognize_mime_type_from_uri(desc,uri) is
+ success(f) then with size = file_size(f),
+ mime_type = if recognize_mime_type_from_uri(desc,uri) is
{
failure then mime("application", "octet-stream", []),
success(mime_type) then mime_type
},
send_file(desc,
- connection,
+ connection,
input_headers,
output_headers,
- size,
- file(f),
+ size,
+ file(f),
uri,
absolute_path,
mime_type,
action_before_send_file)
}
)
- else log_journal_msg(desc,"Cannot find or read authorization file.\n")
+ else log_journal_msg(desc,"Cannot find or read authorization file.\n")
}.
-
-
-
-
-
-
-
-
- *** [5.6] Answering a www-url encoded request.
-
- Standard headers are for answering ".awp" requests.
-
+
+
+
+
+
+
+
+
+ *** [5.6] Answering a www-url encoded request.
+
+ Standard headers are for answering ".awp" requests.
+
public define List(HTTP_header)
standard_headers
=
[
- http_header("Date", format_http_date(now)),
+ http_header("Date", format_http_date(now)),
http_header("Server", "Anubis Embedded Server v" + major_version_number + "." + minor_version_number),
/*http_header("Connection", "close"), */
].
@@ -2385,10 +2385,10 @@ public define List(HTTP_header)
Maybe(String) mb_charset,
) =
[
- http_header("Content-Type", mime_type + if mb_charset is success(charset) then "; charset="+charset else ""),
+ http_header("Content-Type", mime_type + if mb_charset is success(charset) then "; charset="+charset else ""),
http_header("Content-length", to_decimal(answer_body_size))
].
-
+
public define List(HTTP_header)
file_attached_header
(
@@ -2396,34 +2396,34 @@ public define List(HTTP_header)
) =
[
http_header("Content-Disposition", "attachment; filename=\"" + filename +"\"")
- ].
-
+ ].
+
define One
www_url_answer
(
- String host_name,
- Web_Site_Description desc,
+ String host_name,
+ Web_Site_Description desc,
Connection connection, // with the client
Word32 ip_addr, // of the client
HTTP_RequestLine request_line,
- List(HTTP_header) headers,
+ List(HTTP_header) headers,
ByteArray body,
One -> String generate_tt // trust ticket generation
- ) =
- with all_web_args = query_string(request_line) +
+ ) =
+ with all_web_args = query_string(request_line) +
read_www_url_encoded_web_args(to_string(body),0),
uri = uri(request_line),
- ext = get_uri_extension(uri),
+ ext = get_uri_extension(uri),
http_inf = http_info(ip_addr, host_name, uri, headers, is_SSL(connection)),
(if member(journal_extensions(desc),ext)
then log_journal_msg(desc,
format_request(desc,connection,request_line,headers,all_web_args))
- else unique);
- if is_illegal_uri(uri,0)
- then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
+ else unique);
+ if is_illegal_uri(uri,0)
+ then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
else (if (ext = ".awp" | ext = "")
- then (with answer_headers_body = awp_handler(desc)(host_name,
+ then (with answer_headers_body = awp_handler(desc)(host_name,
http_inf,
all_web_args,
is_SSL(connection)),
@@ -2436,7 +2436,7 @@ define One
uri,
headers,
standard_headers,
- if web_arg_value(all_web_args,"zauth") is
+ if web_arg_value(all_web_args,"zauth") is
{
not_found then failure,
found(v) then success(v)
@@ -2444,30 +2444,30 @@ define One
(One u) |-> before_send_file(desc)(http_inf, all_web_args))
//print_delta("After sending file")
)).
-
-
-
-
-
-
-
- *** [5.7] Answering a multipart/form-data encoded request.
-
+
+
+
+
+
+
+
+ *** [5.7] Answering a multipart/form-data encoded request.
+
In order to support upload of files, we must be able to read web arguments which are
encoded in a multipart/form-data body. The first thing to do is to find the
boundary. The boundary is a special string which delimits the various parts of the
'multipart' body. It is found within the value of the 'Content-Type' HTTP header, as
the value of the 'boundary' attribute.
-
-
-
-
-
+
+
+
+
+
*** [5.7.1] Finding the boundary.
-
+
Hence, we just have to find the string 'boundary=' within the value of the
'Content-Type' header, and read the value of the boundary from there.
-
+
define Bool
delimits_boundary
(
@@ -2478,101 +2478,101 @@ define Bool
if c = 10 then true else
if c = 0 then true else
if c = ',' then true else
- c = ';'.
-
-
+ c = ';'.
+
+
define Maybe(String)
get_boundary_value_3
(
- String s,
- Int i,
+ String s,
+ Int i,
List(Word8) so_far
) =
- if nth(i,s) is
+ if nth(i,s) is
{
failure then success(implode(reverse(so_far))),
- success(c) then
+ success(c) then
if delimits_boundary(c)
- then success(implode(reverse(so_far)))
+ then success(implode(reverse(so_far)))
else get_boundary_value_3(s,i+1,[c . so_far])
- }.
-
-
-
+ }.
+
+
+
define Maybe(String)
get_boundary_value_2
(
String s,
Int i,
) =
- if nth(i,s) is
+ if nth(i,s) is
{
- failure then failure,
+ failure then failure,
success(c) then
- if is_blank(c)
+ if is_blank(c)
then get_boundary_value_2(s,i+1)
else get_boundary_value_3(s,i+1,[c])
- }.
-
+ }.
+
define Maybe(String)
get_boundary_value_1
(
String s, // string into which we must find '= ...'
Int i // position of start of search
- ) =
- if nth(i,s) is
+ ) =
+ if nth(i,s) is
{
- failure then failure,
+ failure then failure,
success(c) then
- if is_blank(c)
+ if is_blank(c)
then get_boundary_value_1(s,i+1)
else if c = '='
then get_boundary_value_2(s,i+1)
else failure
}.
-
-
+
+
define Maybe(String)
get_boundary
(
String content_type_header_value
) =
- if find("boundary",content_type_header_value,0) is
+ if find("boundary",content_type_header_value,0) is
{
- failure then failure,
- success(n) then // 'boundary' has been found at position n
+ failure then failure,
+ success(n) then // 'boundary' has been found at position n
get_boundary_value_1(content_type_header_value,n+8)
- }.
-
+ }.
+
define Maybe(String)
get_boundary
(
List(HTTP_header) headers
)=
- if headers is
+ if headers is
{
- [ ] then failure,
- [h . t] then if h is http_header(name,value) then
+ [ ] then failure,
+ [h . t] then if h is http_header(name,value) then
if name = "content-type"
then get_boundary(value)
else get_boundary(t)
- }.
-
-
-
-
-
-
-
-
- *** [5.7.2] Reading attributes from a multipart entity.
-
+ }.
+
+
+
+
+
+
+
+
+ *** [5.7.2] Reading attributes from a multipart entity.
+
Entities in a multipart/form-data body are separated by instances of the string:
-
+
--bbbbb
-
+
where bbbbb is the boundary computed above. Actually, the body has the form:
-
+
--bbbbb
--bbbbb
@@ -2582,273 +2582,273 @@ define Maybe(String)
--bbbbb
--bbbbb
-
-
+
+
We have to extract an entity which is in the body between offsets 'start' and 'end'
(computed when boundaries have been localized). The entity itself is made of two parts:
headers and body. The body is separated from the headers by a blank line. This blank
line (a double crlf) marks the beginning of the body of the entity. Within the headers
of the entity, we look for a 'Content-Disposition' header, which should look like this:
-
+
Content-Disposition: form-data; name="..."; filename="..." crlf
-
+
We are just interested in the name and the file name. Hence we first search
'Content-Disposition', then we search 'name' and read the value, and we do the same for
'filename'.
-
+
If the 'filename' attribute is not present, the web arg is an ordinary one, otherwise,
- it is an uploaded file.
-
+ it is an uploaded file.
+
Below is a variant of 'find' (see 'tools/findstring.anubis'), with an extra 'end'
argument.
-
-define Maybe(Int)
+
+define Maybe(Int)
find
(
- String what,
- ByteArray where,
+ String what,
+ ByteArray where,
Int start,
Int end
) =
- if find(to_byte_array(what),where,start) is
+ if find(to_byte_array(what),where,start) is
{
- failure then failure,
- success(n) then
- if n+length(what) >= end
+ failure then failure,
+ success(n) then
+ if n+length(what) >= end
then failure
else success(n)
}.
-define Maybe(Int)
+define Maybe(Int)
find
(
- String what,
- String where,
+ String what,
+ String where,
Int start,
Int end
) =
- if find_string(where,what,start) is
+ if find_string(where,what,start) is
{
- failure then failure,
- success(n) then
- if n+length(what) >= end
+ failure then failure,
+ success(n) then
+ if n+length(what) >= end
then failure
else success(n)
- }.
-
+ }.
+
define String
read_attribute_value
(
- ByteArray where,
+ ByteArray where,
Int start,
Int end,
List(Word8) so_far
) =
if start >= end then implode(reverse(so_far)) else
- if nth(start,where) is
+ if nth(start,where) is
{
failure then implode(reverse(so_far)),
- success(c) then
+ success(c) then
if c = '\"'
then implode(reverse(so_far))
else read_attribute_value(where,start+1,end,[c . so_far])
}.
-
-define Maybe(String)
+
+define Maybe(String)
find_attribute
(
- String name,
- ByteArray where,
- Int start,
+ String name,
+ ByteArray where,
+ Int start,
Int end
) =
with prefix = name+"=\"",
if find(to_byte_array(prefix),where,start) is
{
failure then failure,
- success(n) then
+ success(n) then
if n+length(prefix) >= end
then failure
else success(read_attribute_value(where,n+length(prefix),end,[]))
}.
-
-
-
+
+
+
define Maybe((String,Maybe(String)))
find_name_and_filename
(
- ByteArray body,
- Int start,
+ ByteArray body,
+ Int start,
Int end
) =
- if find(to_byte_array("Content-Disposition"),body,start) is
+ if find(to_byte_array("Content-Disposition"),body,start) is
{
- failure then failure,
- success(n) then
- if find_attribute("name",body,n+19,end) is
+ failure then failure,
+ success(n) then
+ if find_attribute("name",body,n+19,end) is
{
failure then failure,
- success(name_value) then if find_attribute("filename",body,n+19,end) is
+ success(name_value) then if find_attribute("filename",body,n+19,end) is
{
failure then success((name_value,failure)),
success(filename_value) then success((name_value,success(filename_value)))
}
}
}.
-
-
-
-
-
-
-
-
-
-
- *** [5.7.3] Creating a temporary filename for an uploaded file.
-
- variable Int uploaded_file_count = 0.
-
+
+
+
+
+
+
+
+
+
+
+ *** [5.7.3] Creating a temporary filename for an uploaded file.
+
+ variable Int uploaded_file_count = 0.
+
This variable is local to the virtual machine. Hence, its value is 0 each time a new
requests arrives. Temporary uploaded files are stored in the directory represented by
'upload_temporary_directory'. The filenames have the form:
-
+
_m_n
-
+
where 'm' is the number of the virtual machine, and 'n' a number obtained by
incrementing 'uploaded_file_count'. Notice that the program must do something with this
file (move it to some directory/name), otherwise, it will probably be overwritten the
next time the same machine works.
-
-
-
-
-
-
- *** [5.7.4] Saving an uploaded file under a temporary filename.
-
+
+
+
+
+
+
+ *** [5.7.4] Saving an uploaded file under a temporary filename.
+
define Maybe(String) // returns the temporary file name
save_uploaded_file
(
String web_site_dir,
-// Web_Site_Description desc,
-
+// Web_Site_Description desc,
+
RStream body_fd,
-// ByteArray body,
+// ByteArray body,
Int start,
Int end,
- SState s
+ SState s
) =
- s.uploaded_file_count <- 1 + *(s.uploaded_file_count);
+ s.uploaded_file_count <- 1 + *(s.uploaded_file_count);
with tfn = "_"+to_decimal(virtual_machine_id)+"_"+to_decimal(*(s.uploaded_file_count)),
// println("save_uploaded_file to :"+site_directory(desc)+"/upload_temporary/"+tfn);
// println("start offset = "+start+" end offset = "+end);
//make data_io which is the size of the file to extract
- if copy_Data_IO_to_file(make_data_io(body_fd, start, end - start), web_site_dir+"/upload_temporary/"+tfn) is copy_ok(_)
+ if copy_Data_IO_to_file(make_data_io(body_fd, start, end - start), web_site_dir+"/upload_temporary/"+tfn) is copy_ok(_)
then success(tfn)
else failure
.
-
-
-
-// if (Maybe(RWStream))file(site_directory(desc)+"/upload_temporary/"+tfn, new) is
+
+
+
+// if (Maybe(RWStream))file(site_directory(desc)+"/upload_temporary/"+tfn, new) is
// {
-// failure then failure,
-// success(f) then
-// if reliable_write(file(f),extract(body,start,end)) is
+// failure then failure,
+// success(f) then
+// if reliable_write(file(f),extract(body,start,end)) is
// {
-// failure then failure,
+// failure then failure,
// success(nw) then
// if nw = end - start
// then success(tfn)
// else failure
// }
-// }.
-
-
-
-
-
-
-
-
- *** [5.7.5] Removing the path from a file name.
+// }.
+
+
+
+
+
+
+
+
+ *** [5.7.5] Removing the path from a file name.
When a file is uploaded, the browser sends the complete path of the file on the client
machine as the file name. Actually, this is not quite normal. Nevertheless, we need to
- remove the path, and keep only the file name. This is achieved by 'remove_path' below.
-
+ remove the path, and keep only the file name. This is achieved by 'remove_path' below.
+
define Int
file_name_begin
(
String full_name,
Int i
) =
- if nth(i,full_name) is
+ if nth(i,full_name) is
{
- failure then 0,
- success(c) then
+ failure then 0,
+ success(c) then
if c = '/' then i+1 else
if c = '\\' then i+1 else
file_name_begin(full_name,i-1)
- }.
-
+ }.
+
define String
remove_path
(
String full_name
) =
- with l = length(full_name),
+ with l = length(full_name),
b = file_name_begin(full_name,l-1),
- substr(full_name,b,l-b).
-
-
-
-
-
- *** [5.7.6] Reading a multipart entity.
-
+ substr(full_name,b,l-b).
+
+
+
+
+
+ *** [5.7.6] Reading a multipart entity.
+
define Maybe(Web_arg)
get_multipart_entity
(
- //Web_Site_Description desc,
+ //Web_Site_Description desc,
String web_site_dir,
- String body_temp_file,
+ String body_temp_file,
Int start_offset, //real offset in source file of the part
Int end_offset, //real offset in source file of the part
- SState s
+ SState s
)=
- //Get header of the part
- if find_the_first(body_temp_file, crlf+crlf, start_offset, end_offset) is
+ //Get header of the part
+ if find_the_first(body_temp_file, crlf+crlf, start_offset, end_offset) is
{
- failure then failure,
- success(k) then
+ failure then failure,
+ success(k) then
if (Maybe(RStream))file(body_temp_file, read) is
{
failure then failure,
success(body_fd) then
if read_bytes(make_data_io(body_fd, start_offset, k), k) is success(header_part) then
-
+
if find_name_and_filename(header_part, 0, k) is
{
- failure then failure,
- success(n_mbfn) then if n_mbfn is (name, mbfn) then
- if mbfn is
+ failure then failure,
+ success(n_mbfn) then if n_mbfn is (name, mbfn) then
+ if mbfn is
{
- failure then
+ failure then
if read_bytes(make_data_io(body_fd, start_offset+k+4, (end_offset-2)-(start_offset+k+4)), (end_offset-2)-(start_offset+k+4)) is success(attachement) then
success(web_arg(name,to_string(attachement)))
// we must substract 2 to end because of crlf just before the boundary
else
failure,
-
- success(fn) then
- if save_uploaded_file(web_site_dir, body_fd, start_offset+k+4, end_offset-2, s) is
+
+ success(fn) then
+ if save_uploaded_file(web_site_dir, body_fd, start_offset+k+4, end_offset-2, s) is
{
- failure then failure,
+ failure then failure,
success(tfn) then success(upload(name, remove_path(fn), web_site_dir+"/upload_temporary/"+tfn))
-
+
}
}
}
@@ -2857,17 +2857,17 @@ define Maybe(Web_arg)
}
}.
-
-
+
+
public define List(Web_arg)
read_multipart_form_data_encoded_web_args
(
String web_site_dir,
- //Web_Site_Description desc,
+ //Web_Site_Description desc,
String body_temp_file,
- String __boundary,
+ String __boundary,
Int file_offset,
- SState s
+ SState s
) =
with boundary_length = length(__boundary),
println(
@@ -2876,90 +2876,90 @@ public define List(Web_arg)
body_temp_file : "+body_temp_file+"
file_offset : "+file_offset);
//get the first boundary position
- if find_the_first(body_temp_file, __boundary, file_offset) is
+ if find_the_first(body_temp_file, __boundary, file_offset) is
{
- failure then println("first boundary NOT found");[ ],
- success(first) then
- with first = first + file_offset, //adjust the offset to real offset in file
+ failure then println("first boundary NOT found");[ ],
+ success(first) then
+ with first = first + file_offset, //adjust the offset to real offset in file
//get the second boundary position
- if find_the_first(body_temp_file, __boundary, first + boundary_length) is
+ if find_the_first(body_temp_file, __boundary, first + boundary_length) is
{
- failure then println("last boundary NOT found");[ ],
- success(last) then
- with last = last + first + boundary_length, //adjust the offset to real offset in file
+ failure then println("last boundary NOT found");[ ],
+ success(last) then
+ with last = last + first + boundary_length, //adjust the offset to real offset in file
//Extract the file content here
- if get_multipart_entity(web_site_dir, body_temp_file, first+boundary_length, last, s) is
+ if get_multipart_entity(web_site_dir, body_temp_file, first+boundary_length, last, s) is
{
failure then [ ],
- success(wa) then
- [wa . read_multipart_form_data_encoded_web_args(web_site_dir, body_temp_file, __boundary, last, s)]
+ success(wa) then
+ [wa . read_multipart_form_data_encoded_web_args(web_site_dir, body_temp_file, __boundary, last, s)]
}
}
}.
-
-
-
+
+
+
define One
multipart_form_data_answer
(
String host_name,
- Web_Site_Description desc,
- Connection connection,
- Word32 ip_addr,
- HTTP_RequestLine request_line,
- List(HTTP_header) headers,
+ Web_Site_Description desc,
+ Connection connection,
+ Word32 ip_addr,
+ HTTP_RequestLine request_line,
+ List(HTTP_header) headers,
String body_temp_file,
One -> String generate_tt,
- SState s
- ) =
- if get_boundary(headers) is
+ SState s
+ ) =
+ if get_boundary(headers) is
{
- failure then unique,
- success(boundary) then
- with all_web_args = query_string(request_line) +
+ failure then unique,
+ success(boundary) then
+ with all_web_args = query_string(request_line) +
read_multipart_form_data_encoded_web_args(desc.site_directory,
body_temp_file,
"--"+boundary,
0,
s),
- uri = uri(request_line),
- ext = get_uri_extension(uri),
+ uri = uri(request_line),
+ ext = get_uri_extension(uri),
log_journal_msg(desc,
- format_request(desc,connection,request_line,headers,all_web_args));
- if is_illegal_uri(uri,0)
- then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
+ format_request(desc,connection,request_line,headers,all_web_args));
+ if is_illegal_uri(uri,0)
+ then log_journal_msg(desc,"Received illegal URI: "+uri+"\n")
else
- if (ext = ".awp" | ext = "") then
- (with answer_headers_body = awp_handler(desc)(host_name,
+ if (ext = ".awp" | ext = "") then
+ (with answer_headers_body = awp_handler(desc)(host_name,
http_info(ip_addr, host_name, uri, headers, is_SSL(connection)),
all_web_args,
is_SSL(connection)),
forget(reliable_write(connection, answer_headers_body)))
else unique
- }.
-
-
-
-
-
-
-
-
- *** [5.8] Handling redirections.
-
+ }.
+
+
+
+
+
+
+
+
+ *** [5.8] Handling redirections.
+
'redirections' (of type 'List(Redirection)') contains redirection directives. Each one
has the form:
-
- redirect(required_uri,required_host,corresponding_uri).
-
+
+ redirect(required_uri,required_host,corresponding_uri).
+
The host required by the client may be found in the 'Host' HTTP header. The URI
required by the client is given below as 'uri'. We just have to find the required host
in the headers, and to find the corresponding redirection directive.
-
-
+
+
In the next fonction, the required host and URI are known. We just have to search in
the 'redirections' list.
-
+
define String
handle_redirection
(
@@ -2967,50 +2967,50 @@ define String
String required_host,
List(Redirection) redirections
) =
- if redirections is
+ if redirections is
{
[ ] then required_uri,
- [h . t] then if h is redirect(uri,host,target) then
+ [h . t] then if h is redirect(uri,host,target) then
if host = required_host
then if uri = required_uri
then target
else handle_redirection(required_uri,required_host,t)
else handle_redirection(required_uri,required_host,t)
- }.
-
-
-
+ }.
+
+
+
The host name may be encumbered by a port number, like
-
+
www.our-business.com:1607
-
- We must remove this port number, otherwise the host name may not be recognized.
-
-define String
+
+ We must remove this port number, otherwise the host name may not be recognized.
+
+define String
strip_port
(
String name,
Int i
) =
- if nth(i,name) is
+ if nth(i,name) is
{
failure then name,
- success(c) then
+ success(c) then
if c = ':'
then substr(name,0,i)
else strip_port(name,i+1)
- }.
-
-
-
-
-
- Finding the 'Host' header. No redirection is performed if this header is not found.
-
-define String
+ }.
+
+
+
+
+
+ Finding the 'Host' header. No redirection is performed if this header is not found.
+
+define String
handle_redirection // returns the redirected URI
(
- Redirections redirections,
+ Redirections redirections,
String uri, // original URI
List(HTTP_header) headers
)=
@@ -3022,125 +3022,125 @@ define String
{
redirection_list(l) then handle_redirection(uri, host, l)
redirection_fn(f) then f(uri, host)
- }
+ }
}.
-
-
-
-
-
-
- *** [5.9] Answering both sorts of requests.
-
+
+
+
+
+
+
+ *** [5.9] Answering both sorts of requests.
+
We must decide if the request is www-url encoded or multipart/form-data encoded. This
is achieved through the header 'Content-Type'.
-
+
define EncodingType
get_encoding_type
(
List(HTTP_header) headers
) =
- if headers is
+ if headers is
{
- [ ] then www_url, // this is the default
- [h . t] then if h is http_header(name, value) then
+ [ ] then www_url, // this is the default
+ [h . t] then if h is http_header(name, value) then
if name = "content-type"
- then if find("multipart/form-data",value,0) is
+ then if find("multipart/form-data",value,0) is
{
failure then www_url,
success(_) then multipart_form_data
}
- else
+ else
get_encoding_type(t) //content-type not found, check the next header line
}.
-
-
-
+
+
+
define One
send_answer
(
- String host_name,
- Web_Site_Description desc,
- Connection connection,
- HTTP_RequestLine rqline,
- List(HTTP_header) headers,
+ String host_name,
+ Web_Site_Description desc,
+ Connection connection,
+ HTTP_RequestLine rqline,
+ List(HTTP_header) headers,
ByteArray body,
One -> String generate_tt,
- SState s
- )=
- if rqline is request_line(type, uri, qstring) then
- with rqline2 = request_line(type, handle_redirection(redirections(desc), uri, headers), qstring),
+ SState s
+ )=
+ if rqline is request_line(type, uri, qstring) then
+ with rqline2 = request_line(type, handle_redirection(redirections(desc), uri, headers), qstring),
if remote_IP_address_and_port(connection) is (ip_addr,_) then
- if get_encoding_type(headers) is
+ if get_encoding_type(headers) is
{
www_url then www_url_answer(host_name, desc, connection, ip_addr, rqline2, headers, body, generate_tt),
multipart_form_data then multipart_form_data_answer(host_name, desc, connection, ip_addr, rqline2, headers, body, generate_tt, s)
}.
-
-
-
-
-
-
-
- *** [6] The HTTP/HTTPS server.
+
+
+
+
+
+
+
+ *** [6] The HTTP/HTTPS server.
The command 'start_server' (declared in 'predefined.anubis') starts a virtual machine
which opens a server TCP/IP connection, and which continuously listens to this
connection. When a request arrives, this machine delegates the work of deciphering and
answering the request to another virtual machine, and continues to listen. The job of
- the delegated machine is defined by the HTTP request handler below.
-
+ the delegated machine is defined by the HTTP request handler below.
+
-
- *** [6.1] Determining the requested host.
-
+
+ *** [6.1] Determining the requested host.
+
When a request arrives to one of our two servers, we must decide which site (host) is
requested.
-
-define Maybe(String)
+
+define Maybe(String)
get_host_header_value
(
List(HTTP_header) headers
) =
- if headers is
+ if headers is
{
- [ ] then failure,
- [h . t] then if h is http_header(name,value) then
+ [ ] then failure,
+ [h . t] then if h is http_header(name,value) then
if name = "host"
then success(strip_port(value,0))
else get_host_header_value(t)
- }.
-
+ }.
+
define Maybe((String,Web_Site_Description))
get_site
(
- String requested_host,
+ String requested_host,
List(Web_Site_Description) sites
) =
- if sites is
+ if sites is
{
- [ ] then print("Requested host '"+requested_host+"' does not exist.\n"); failure,
- [site1 . others] then
- if site1 is web_site_description(common_names,_,_,_,_,_,_,_,_,_) then
+ [ ] then print("Requested host '"+requested_host+"' does not exist.\n"); failure,
+ [site1 . others] then
+ if site1 is web_site_description(common_names,_,_,_,_,_,_,_,_,_) then
if member(common_names,requested_host)
then success((requested_host,site1))
else get_site(requested_host,others)
}.
-
-
+
+
define Maybe((String,Web_Site_Description))
get_site
(
- List(HTTP_header) headers,
+ List(HTTP_header) headers,
List(Web_Site_Description) sites
) =
- if get_host_header_value(headers) is
+ if get_host_header_value(headers) is
{
- failure then print("No 'Host' HTTP header.\n"); failure,
- success(requested_host) then
+ failure then print("No 'Host' HTTP header.\n"); failure,
+ success(requested_host) then
//here we treat the case with only one site. hence we accept any host request
//print("*** there is " +length(sites) + " sites \n");
if length(sites) = 1 then
@@ -3152,90 +3152,90 @@ define Maybe((String,Web_Site_Description))
}
else
get_site(requested_host,sites)
- }.
-
-
-
-
-
- *** [6.2] The HTTP request handler.
-
+ }.
+
+
+
+
+
+ *** [6.2] The HTTP request handler.
+
Here is the HTTP/HTTPS handler. It is called at each new request in a separate virtual
machine. It reads the headers of the HTTP request, determines the host, determines body
size, reads the body of the HTTP request, and answers the request.
-
-
-
-define One -> String make_generate_trust_ticket(DenialOfService dos).
-
+
+
+
+define One -> String make_generate_trust_ticket(DenialOfService dos).
+
define One
http_https_handler
(
List(Web_Site_Description) sites,
- BufferedConnection connection,
+ BufferedConnection connection,
Bool is_https,
DenialOfService dos,
SState s
) =
//t0 <- (UTime)unow;
- with start_time = (Int)now,
- s.sttm <- start_time;
+ with start_time = (Int)now,
+ s.sttm <- start_time;
//println("Request time: " + format_http_date(start_time));
- if dos is denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
- if remote_IP_address_and_port(connection.conn) is (ip_addr,port) then
- if read_request_line(connection, start_time+*rld_v, dos, s) is
+ if dos is denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
+ if remote_IP_address_and_port(connection.conn) is (ip_addr,port) then
+ if read_request_line(connection, start_time+*rld_v, dos, s) is
{
error(msg) then print(format(msg)),
ok(rqline) then //request line
//print_delta("read_request_line");
- if read_http_headers(connection, start_time+*hd_v, dos, s) is
+ if read_http_headers(connection, start_time+*hd_v, dos, s) is
{
- error(msg) then print(format(msg)),
- ok(headers) then //print_delta("read_http_headers");
+ error(msg) then print(format(msg)),
+ ok(headers) then //print_delta("read_http_headers");
if get_site(headers,sites) is
{
failure then unique,
- success(p) then if p is (host_name, desc) then
+ success(p) then if p is (host_name, desc) then
//Body Size
- if get_body_size(headers) is
+ if get_body_size(headers) is
{
error(msg) then log_journal_msg(desc,format(msg)),
- ok(body_size) then
- if rqline is request_line(type, uri, qstring) then
- with rqline2 = request_line(type, handle_redirection(redirections(desc), uri, headers), qstring),
+ ok(body_size) then
+ if rqline is request_line(type, uri, qstring) then
+ with rqline2 = request_line(type, handle_redirection(redirections(desc), uri, headers), qstring),
//Get the type of encoding which decide if we read the content in ByteArray for www_url or in
//temporary file for multipart.
with generate_tt = make_generate_trust_ticket(dos),
- if get_encoding_type(headers) is
+ if get_encoding_type(headers) is
{
//WWW_URL
- www_url then
- if read_http_body(connection, body_size, constant_byte_array(0,0), 1000) is
+ www_url then
+ if read_http_body(connection, body_size, constant_byte_array(0,0), 1000) is
{
- error(msg) then log_journal_msg(desc,format(msg)),
- ok(body) then
+ error(msg) then log_journal_msg(desc,format(msg)),
+ ok(body) then
www_url_answer(host_name, desc, connection.conn, ip_addr, rqline2, headers, body, generate_tt);
//it's HTTP 1.1 keep-alive is default
http_https_handler(sites, connection, is_https, dos, s)
}
- //MULTIPART_FORM_DATA
- multipart_form_data then
+ //MULTIPART_FORM_DATA
+ multipart_form_data then
- if body_size > 0 then
+ if body_size > 0 then
with t0 = (UTime)unow,
-
+
if get_socket_from_connection(conn(connection)) is
{
failure then println("can't get socket"),
success(source) then
-
+
with tmp_body_file = "/temp/" + virtual_machine_id + "-" + now,
if (Maybe(RWStream))file(desc.site_directory + tmp_body_file, new) is
{
failure then println("can't create target file "+desc.site_directory + tmp_body_file),//nothing to write
- success(target) then
-
+ success(target) then
+
//get the content of the current buffer and unput char list
with buffer = get_and_erase_buffer(connection, s),
buffer_size = length(buffer),
@@ -3244,24 +3244,24 @@ define One
if flush(buffer, weaken(target)) is
{
failure then println("Can't flush the buffer"),
- success(_) then
- if copy_file(weaken(source), weaken(target), body_size - buffer_size) is copy_ok(read_size) then
-
+ success(_) then
+ if copy_file(weaken(source), weaken(target), body_size - buffer_size) is copy_ok(read_size) then
+
with duration = (UTime) unow - t0,
println("Read body "+read_size+" duration: " + __utime_to_string(duration));
-
+
multipart_form_data_answer(host_name, desc, connection.conn, ip_addr, rqline2, headers, desc.site_directory + tmp_body_file, generate_tt, s);
-
+
//it's HTTP 1.1 keep-alive is default
- http_https_handler(sites, connection, is_https, dos, s)
-
+ http_https_handler(sites, connection, is_https, dos, s)
+
else
println("Can't copy data from stream to temporary file ")
}}}
else
println("body_size = 0 !")
}
-
+
//print_delta("before send_answer");
// with body = constant_byte_array(0,0),
// send_answer(host_name, desc,connection.conn, request_line, headers, body,
@@ -3272,29 +3272,29 @@ define One
//println("Request duration: " + __utime_to_string(duration))
//println("BufferRead duration: " + __utime_to_string(*t1));
//println("next_char duration: " + __utime_to_string(*t2))
-
+
}
}
}
}.
-
-
+
+
Below are the two tools for constructing the handlers required by 'start_server' and
'start_ssl_server' (see 'predefined.anubis').
-
-define Bool is_dubious_IP(Word32 ip, DenialOfService dos).
-
+
+define Bool is_dubious_IP(Word32 ip, DenialOfService dos).
+
define Server -> ((RWStream) -> One)
make_http_handler
(
- List(Web_Site_Description) sites,
- DenialOfService dos
+ List(Web_Site_Description) sites,
+ DenialOfService dos
) =
- (Server server) |-> (RWStream conn) |->
- if remote_IP_address_and_port(conn) is (addr,_) then
+ (Server server) |-> (RWStream conn) |->
+ if remote_IP_address_and_port(conn) is (addr,_) then
if is_dubious_IP(addr,dos)
then print("Rejecting dubious IP address "+ip_addr_to_string(addr)+"\n")
- else
+ else
with connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
http_https_handler(sites, connection, false, dos, sstate(var([]),var(0),var(0))).
public define One
@@ -3304,51 +3304,51 @@ public define One
RWStream conn,
DenialOfService dos
) =
- if remote_IP_address_and_port(conn) is (addr,_) then
+ if remote_IP_address_and_port(conn) is (addr,_) then
if is_dubious_IP(addr,dos)
then print("Rejecting dubious IP address "+ip_addr_to_string(addr)+"\n")
- else
+ else
with connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
http_https_handler(sites, connection, false, dos, sstate(var([]),var(0),var(0))).
-
+
define Server -> (SSL_Connection -> One)
make_https_handler
(
- List(Web_Site_Description) sites,
+ List(Web_Site_Description) sites,
DenialOfService dos
) =
- (Server server) |-> (SSL_Connection conn) |->
+ (Server server) |-> (SSL_Connection conn) |->
with connection = buffered_connection(ssl(conn), var(constant_byte_array(0, 0)), var(0)),
- http_https_handler(sites, connection, true, dos, sstate(var([]),var(0),var(0))).
-
-
-
-
- *** [6.3] Server's tasks.
-
+ http_https_handler(sites, connection, true, dos, sstate(var([]),var(0),var(0))).
+
+
+
+
+ *** [6.3] Server's tasks.
+
Some tasks must be executed periodically, for example for cleaning up directories from
short life time files.
The next function removes from the given directory (and recursively from its
subdirectories) all the files which are more than 10 minutes old.
-
-define One
+
+define One
cleanup_directory_10mn
(
String dir // path of private download directory (or subdirectory) with trailing slash
) =
- forget(map((FileDescription fd) |-> if fd is
+ forget(map((FileDescription fd) |-> if fd is
{
- no_info(name) then forget(remove(dir+name)),
+ no_info(name) then forget(remove(dir+name)),
file(name,_,_,d) then if to_Int(d)+600 < now then forget(remove(dir+name)) else unique,
link(name,_,_,d) then if to_Int(d)+600 < now then forget(remove(dir+name)) else unique,
- directory(name,_,_) then cleanup_directory_10mn(dir+name+"/"),
+ directory(name,_,_) then cleanup_directory_10mn(dir+name+"/"),
},
- directory_full_list(dir,"*","*","*"))).
-
-
-define One
+ directory_full_list(dir,"*","*","*"))).
+
+
+define One
http_servers_tasks
(
List(Web_Site_Description) sites,
@@ -3359,22 +3359,22 @@ define One
if mapand(is_down,servers)
then unique
else if now > next_time
- then
+ then
(
/*
- forget(map((Web_Site_Description wsd) |->
+ forget(map((Web_Site_Description wsd) |->
cleanup_directory_10mn(site_directory(wsd)+"/private_download/"),
- sites));
- */
+ sites));
+ */
http_servers_tasks(sites,servers,period,next_time+period)
)
else
(
- sleep(1000);
+ sleep(1000);
http_servers_tasks(sites,servers,period,next_time)
).
-
-
+
+
public define One
start_http_servers_tasks
(
@@ -3382,91 +3382,91 @@ public define One
List(Server) servers,
Int period
) =
- delegate http_servers_tasks(sites,servers,period,now),
- unique.
-
-
-
-
- *** [6.4] Protection against 'denial of service' attacks.
-
-
- *** [6.4.1] Counting connections.
-
+ delegate http_servers_tasks(sites,servers,period,now),
+ unique.
+
+
+
+
+ *** [6.4] Protection against 'denial of service' attacks.
+
+
+ *** [6.4.1] Counting connections.
+
define Bool // returns false if the counter cannot be incremented (too many connections)
increment_connections_counter
(
Var(Int) counter
) =
- protect with n = *counter,
+ protect with n = *counter,
if n >= 100
then false
- else (counter <- (*counter)+1); true.
-
+ else (counter <- (*counter)+1); true.
+
define One
decrement_connections_counter
(
Var(Int) counter
) =
- protect counter <- (*counter)-1.
-
-
-
-
-
+ protect counter <- (*counter)-1.
+
+
+
+
+
*** [6.4.2] Recording dubious IP addresses.
-
-
+
+
define List(DubiousIP)
record_dubious_IP
(
Word32 ip,
List(DubiousIP) l
) =
- if l is
+ if l is
{
[ ] then [dubious_ip(ip,now)],
- [h . t] then if h is dubious_ip(addr,time) then
+ [h . t] then if h is dubious_ip(addr,time) then
if addr = ip
then [dubious_ip(addr,now) . t]
else [h . record_dubious_IP(ip,t)]
}.
-
-
+
+
define One
record_dubious_IP
(
Word32 dubious_IP,
Var(List(DubiousIP)) v
) =
- protect v <- record_dubious_IP(dubious_IP,*v).
-
-
+ protect v <- record_dubious_IP(dubious_IP,*v).
+
+
define One
record_dubious_IP
(
- Word32 addr,
+ Word32 addr,
DenialOfService dos
) =
- record_dubious_IP(addr,list_of_dubious(dos)).
-
-
-public define DenialOfService
+ record_dubious_IP(addr,list_of_dubious(dos)).
+
+
+public define DenialOfService
load_denial_of_service_info
=
- if (RetrieveResult(DenialOfService))retrieve(my_anubis_directory+"/web_sites/dos_info") is
+ if (RetrieveResult(DenialOfService))retrieve(my_anubis_directory+"/web_sites/dos_info") is
ok(dos) then dos else denial_of_service(
var(100),
var(1000),
- var(1500),
- var(2000),
+ var(1500),
+ var(2000),
var([]),
- var([])).
-
-
-
-
- *** [6.4.3] Testing if an address is dubious.
+ var([])).
+
+
+
+
+ *** [6.4.3] Testing if an address is dubious.
define Bool
is_dubious_IP
@@ -3474,41 +3474,41 @@ define Bool
Word32 ip,
List(DubiousIP) l
) =
- if l is
+ if l is
{
- [ ] then false,
- [h . t] then if h is dubious_ip(addr,time) then
+ [ ] then false,
+ [h . t] then if h is dubious_ip(addr,time) then
if ip = addr
then true
else is_dubious_IP(ip,t)
}.
-
-
+
+
define Bool
is_dubious_IP
(
Word32 ip,
DenialOfService dos
) =
- if dos is
+ if dos is
{
- denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
+ denial_of_service(mc_v,rld_v,hd_v,ad_v,ld_v,ra_v) then
if member(*ra_v,ip) then false else
is_dubious_IP(ip,*ld_v)
- }.
-
-
-
-
- *** [6.4.4] Removing inactive dubious IP addresses.
-
+ }.
+
+
+
+
+ *** [6.4.4] Removing inactive dubious IP addresses.
+
define List(DubiousIP)
remove_inactive_dubious_IP
(
List(DubiousIP) l,
Int ref_time,
) =
- if l is
+ if l is
{
[ ] then [ ],
[h . t] then if h is dubious_ip(addr,time) then
@@ -3516,8 +3516,8 @@ define List(DubiousIP)
then (print(ip_addr_to_string(addr)+" removed from dubious addresses list.\n");
remove_inactive_dubious_IP(t,ref_time))
else [h . remove_inactive_dubious_IP(t,ref_time)]
- }.
-
+ }.
+
define One
remove_inactive_dubious_IP
(
@@ -3525,62 +3525,62 @@ define One
) =
protect
with ref_time = (Int)now - 600, // 10 minutes
- v <- remove_inactive_dubious_IP(*v,ref_time).
-
-
- The above function will be executed periodically by the servers's tasks machine.
-
-
-
- *** [6.4.5] Making the function for generating trust tickets.
-
-define One -> String
+ v <- remove_inactive_dubious_IP(*v,ref_time).
+
+
+ The above function will be executed periodically by the servers's tasks machine.
+
+
+
+ *** [6.4.5] Making the function for generating trust tickets.
+
+define One -> String
make_generate_trust_ticket
(
DenialOfService dos
) =
- (One _) |-> "".
-
-
-
-
-
-
-
- *** [6.5] Starting the HTTP/HTTPS server.
-
-
+ (One _) |-> "".
+
+
+
+
+
+
+
+ *** [6.5] Starting the HTTP/HTTPS server.
+
+
The next function creates the directories for all sites (if they don't already exist).
-
+
define One
create_directories
(
List(Web_Site_Description) sites
) =
- if sites is
+ if sites is
{
- [ ] then unique,
- [s1 . others] then
- with site_dir = site_directory(s1),
+ [ ] then unique,
+ [s1 . others] then
+ with site_dir = site_directory(s1),
forget(make_directory(site_dir+"/public",default_directory_mode));
forget(make_directory(site_dir+"/upload_temporary",default_directory_mode));
forget(make_directory(site_dir+"/private_download",default_directory_mode));
forget(make_directory(site_dir+"/journal",default_directory_mode));
create_directories(others)
- }.
-
-
-
-
-
- Below are the commands for starting an HTTP server and an HTTPS server.
-
-
+ }.
+
+
+
+
+
+ Below are the commands for starting an HTTP server and an HTTPS server.
+
+
define StartServerResult
start_http_server
(
Word32 ip_address,
- Word32 port,
+ Word32 port,
Server -> ((RWStream) -> One) handler,
Int retries,
DenialOfService dos
@@ -3588,160 +3588,160 @@ define StartServerResult
if start_server(ip_address,
port,
handler,
- identity) is ok(server)
- then print(" \r");
+ identity) is ok(server)
+ then print(" \r");
ok(server)
- else print("Port "+port+": retry number "+retries+"\r");
- sleep(1000);
- start_http_server(ip_address,port,handler,retries+1,dos).
-
+ else print("Port "+port+": retry number "+retries+"\r");
+ sleep(1000);
+ start_http_server(ip_address,port,handler,retries+1,dos).
+
public define StartServerResult
start_http_server
(
Word32 ip_address,
- Word32 port,
+ Word32 port,
List(Web_Site_Description) sites,
- DenialOfService dos
+ DenialOfService dos
) =
create_directories(sites);
start_http_server(ip_address,port,
make_http_handler(sites, dos),
0,
- dos).
-
-
+ dos).
+
+
For the HTTPS server, we have a problem which is due to the fact that 'anbexec' is not
yet able to manipulate several SSL server certificates. 'anbexec' and
'predefined.anubis' must be changed. Sorry ! This will be done as soon as possible. The
'solution' for the time being is to provide the common name of the unique SSL server
certificate.
-
-
+
+
define StartServerResult
start_https_server
(
Word32 ip_address,
- Word32 port,
+ Word32 port,
String certificate_common_name,
Server -> (SSL_Connection -> One) handler,
Int retries,
DenialOfService dos
- ) =
+ ) =
if start_ssl_server(ip_address,
port,
- certificate_common_name,
+ certificate_common_name,
handler,
- identity) is ok(server)
- then print(" \r");
+ identity) is ok(server)
+ then print(" \r");
ok(server)
- else print("Port "+port+": retry number "+retries+"\r");
- sleep(1000);
+ else print("Port "+port+": retry number "+retries+"\r");
+ sleep(1000);
start_https_server(ip_address,port,
certificate_common_name,
handler,retries+1,
- dos).
+ dos).
+
-
public define StartServerResult
start_https_server
(
Word32 ip_address,
- Word32 port,
+ Word32 port,
String certificate_common_name, // of SSL server certificate
List(Web_Site_Description) sites,
- DenialOfService dos
+ DenialOfService dos
) =
create_directories(sites);
start_https_server(ip_address,port,certificate_common_name,
make_https_handler(sites, dos),
- 0,dos).
-
-
-
-
-
-
-
-
-
- *** [7] The web dispatcher.
-
-
- *** [7.1] The dispatcher server.
-
+ 0,dos).
+
+
+
+
+
+
+
+
+
+ *** [7] The web dispatcher.
+
+
+ *** [7.1] The dispatcher server.
+
define One
send_dispatching_page
(
- RWStream conn,
- String common_name,
+ RWStream conn,
+ String common_name,
Word32 port
) =
- print("Dispatching '"+common_name+"' to port "+port+"\n");
+ print("Dispatching '"+common_name+"' to port "+port+"\n");
forget(reliable_write(conn,to_byte_array(
" "
))).
-
-
-
-define Maybe(DispatcherInfo)
+
+
+
+define Maybe(DispatcherInfo)
find_host
(
List(DispatcherInfo) l,
String host
) =
- if l is
+ if l is
{
- [ ] then failure,
- [h . t] then if h is site(name,port) then
+ [ ] then failure,
+ [h . t] then if h is site(name,port) then
if name = host
then success(h)
else find_host(t,host)
}.
-
-
-
+
+
+
define Server -> ((RWStream) -> One)
make_dispatcher_handler
(
Var(List(DispatcherInfo)) info_v,
DenialOfService dos,
- SState ss
+ SState ss
) =
- (Server server) |-> (RWStream conn) |->
+ (Server server) |-> (RWStream conn) |->
with start_time = (Int)now,
connection = buffered_connection(tcp(conn), var(constant_byte_array(0, 0)), var(0)),
- if read_request_line(connection, start_time+*request_line_delay(dos), dos, ss) is
+ if read_request_line(connection, start_time+*request_line_delay(dos), dos, ss) is
{
- error(msg) then print(format(msg)),
- ok(request_line) then
- if read_http_headers(connection, start_time+*headers_delay(dos), dos, ss) is
+ error(msg) then print(format(msg)),
+ ok(request_line) then
+ if read_http_headers(connection, start_time+*headers_delay(dos), dos, ss) is
{
error(msg) then print(format(msg)),
- ok(headers) then if get_host_header_value(headers) is
+ ok(headers) then if get_host_header_value(headers) is
{
failure then print("No 'HOST' HTTP header.\n"),
- success(host) then
- if find_host(*info_v,host) is
+ success(host) then
+ if find_host(*info_v,host) is
{
failure then print("Host: '"+host+"' not registered.\n"),
- success(s) then if s is site(common_name,ip_port) then
+ success(s) then if s is site(common_name,ip_port) then
send_dispatching_page(conn,common_name,ip_port)
}
}
}
}.
-
-
+
+
define One
dispatcher_update_error
(
String file_path
) =
- print("web_dispatcher: unable to reread file: '"+file_path+"'.\n").
-
-
+ print("web_dispatcher: unable to reread file: '"+file_path+"'.\n").
+
+
define Bool
dispatcher_update_data
(
@@ -3751,104 +3751,104 @@ define Bool
) =
if directory_full_list(my_anubis_directory+"/web_sites","dispatcher.info","","") is
{
- [ ] then false,
- [h . t] then if h is
+ [ ] then false,
+ [h . t] then if h is
{
- no_info(n) then false,
+ no_info(n) then false,
file(n,_,_,d) then if n = "dispatcher.info"
- then (info_date_v <- to_Int(d);
+ then (info_date_v <- to_Int(d);
if (RetrieveResult(List(DispatcherInfo)))retrieve(info_file_path) is
{
- cannot_find_file then false,
- read_error then false,
- type_error then false,
+ cannot_find_file then false,
+ read_error then false,
+ type_error then false,
ok(info) then info_v <- info; true
})
- else false,
- link(_,_,_,_) then false,
+ else false,
+ link(_,_,_,_) then false,
directory(_,_,_) then false
- }
+ }
}.
-
-
-
+
+
+
The loop within which the dispatcher updates its data every 3 seconds:
-
+
define One
dispatcher_update_task
(
String info_file_path,
Var(List(DispatcherInfo)) info_v,
- Var(Int) info_date_v
+ Var(Int) info_date_v
) =
- sleep(3000);
+ sleep(3000);
(if dispatcher_update_data(info_file_path,info_v,info_date_v)
then unique
else dispatcher_update_error(info_file_path));
- dispatcher_update_task(info_file_path,info_v,info_date_v).
-
-
+ dispatcher_update_task(info_file_path,info_v,info_date_v).
+
+
public define One
start_web_dispatcher
(
Word32 ip_address, // address for listening (typically 0: listen on all interfaces)
Word32 http_port, // typically 80
DenialOfService dos,
- SState s
+ SState s
) =
- with info_file_path = my_anubis_directory+"/web_sites/dispatcher.info",
- info_v = var((List(DispatcherInfo))[]),
+ with info_file_path = my_anubis_directory+"/web_sites/dispatcher.info",
+ info_v = var((List(DispatcherInfo))[]),
info_date_v = var((Int)0),
if dispatcher_update_data(info_file_path,info_v,info_date_v)
then if start_server(ip_address,
http_port,
make_dispatcher_handler(info_v, dos, s),
- (One u)|->u) is
+ (One u)|->u) is
{
- cannot_create_the_socket then
+ cannot_create_the_socket then
print("Cannot create the socket for HTTP server.\n"),
- cannot_bind_to_port then
+ cannot_bind_to_port then
print("Cannot bind HTTP server to port "+http_port+".\n"),
- cannot_listen_on_port then
+ cannot_listen_on_port then
print("HTTP server cannot listen on port "+http_port+".\n"),
- ok(http_server) then
+ ok(http_server) then
dispatcher_update_task(info_file_path,info_v,info_date_v)
}
else dispatcher_update_error(info_file_path).
-
-
-
- *** [7.2] The dispatcher web site.
-
- global define One
+
+
+
+ *** [7.2] The dispatcher web site.
+
+ global define One
web_dispatcher
(
List(String) args
) =
- start_web_dispatcher(0,80,load_denial_of_service_info).
-
-
-
-
-
-
- *** [7.3] Managing the info file.
-
+ start_web_dispatcher(0,80,load_denial_of_service_info).
+
+
+
+
+
+
+ *** [7.3] Managing the info file.
+
define Word32
register_ip_address
=
- if ip_address(prompt(" numerical IP address (for HTTP): ")) is
+ if ip_address(prompt(" numerical IP address (for HTTP): ")) is
{
failure then print(" *** Error: incorrect IP address.\n");
register_ip_address,
success(n) then n
}.
-
-
+
+
define Word32
register_ip_port
=
- if decimal_scan(prompt(" IP port (for HTTP): ")) is
+ if decimal_scan(prompt(" IP port (for HTTP): ")) is
{
failure then print(" *** Error: incorrect IP port.\n");
register_ip_port,
@@ -3857,109 +3857,109 @@ define Word32
else print(" *** Error: IP port out of bounds.\n");
register_ip_port
}.
-
-
+
+
define One
register_new_site
(
Var(List(DispatcherInfo)) info_v
) =
- print("\n");
+ print("\n");
print(" Registering a new site:\n");
- with name = prompt(" Site name: "),
+ with name = prompt(" Site name: "),
with addr = register_ip_address,
with port = register_ip_port,
(protect info_v <- [site(name,port) . *info_v]);
print(" Site "+name+" at "+ip_addr_to_string(addr)+":"+port+" added\n (but not saved to disk).\n").
-
-
-define List(DispatcherInfo)
+
+
+define List(DispatcherInfo)
find_sites
(
List(DispatcherInfo) l,
String name
) =
- if l is
+ if l is
{
- [ ] then [ ],
- [h . t] then if h is site(n,_) then
- if find(name,n,0) is
+ [ ] then [ ],
+ [h . t] then if h is site(n,_) then
+ if find(name,n,0) is
{
failure then find_sites(t,name),
success(_) then [h . find_sites(t,name)]
}
- }.
-
+ }.
+
define String
pad
(
- String s,
+ String s,
Int l
) =
if length(s) >= l
then s
- else s+constant_string(l-length(s),' ').
-
-
-
+ else s+constant_string(l-length(s),' ').
+
+
+
define One
show_sites_1
(
- List(DispatcherInfo) l,
+ List(DispatcherInfo) l,
Int i
) =
- if l is
+ if l is
{
- [ ] then unique,
- [h . t] then if h is site(name,port) then
+ [ ] then unique,
+ [h . t] then if h is site(name,port) then
print(" ["+i+"] "+pad(name,40)+" "+" "+port+"\n");
show_sites_1(t,i+1)
}.
-
-
+
+
define One
show_sites
(
- List(DispatcherInfo) l,
+ List(DispatcherInfo) l,
Int i
) =
print(" Name Port\n");
print(" --------------------------------------------------------\n");
- show_sites_1(l,i).
-
+ show_sites_1(l,i).
+
define List(DispatcherInfo)
replace_info
(
- List(DispatcherInfo) l,
+ List(DispatcherInfo) l,
String site_name,
Word32 new_port
- ) =
- if l is
+ ) =
+ if l is
{
- [ ] then print("ALERT: Empty list into replace_info() [" + __FILE__ + "]\n"); [],
- [h . t] then if h is site(n,_) then
+ [ ] then print("ALERT: Empty list into replace_info() [" + __FILE__ + "]\n"); [],
+ [h . t] then if h is site(n,_) then
if n = site_name
- then [site(n,new_port) . t]
+ then [site(n,new_port) . t]
else [h . replace_info(t,site_name,new_port)]
}.
-
+
define List(DispatcherInfo)
delete_info
(
- List(DispatcherInfo) l,
+ List(DispatcherInfo) l,
String site_name,
- ) =
- if l is
+ ) =
+ if l is
{
- [ ] then print("ALERT: Empty list into delete_info() [" + __FILE__ + "]\n"); [],
- [h . t] then if h is site(n,_) then
+ [ ] then print("ALERT: Empty list into delete_info() [" + __FILE__ + "]\n"); [],
+ [h . t] then if h is site(n,_) then
if n = site_name
- then t
+ then t
else [h . delete_info(t,site_name)]
}.
-
-
+
+
define One
update_site
(
@@ -3969,54 +3969,54 @@ define One
) =
print("\n");
print(" Updating site '"+site_name+"': (currently: "+old_port+")\n");
- with new_port = register_ip_port,
+ with new_port = register_ip_port,
answer = prompt(" Update '"+site_name+"' as: "+new_port+" [Y/N] ? "),
if (answer = "Y" | answer = "y")
then info_v <- replace_info(*info_v,site_name,new_port)
- else unique.
-
-
-
+ else unique.
+
+
+
define Bool
compare
(
- DispatcherInfo d1,
+ DispatcherInfo d1,
DispatcherInfo d2
) =
- if d1 is site(n1,_) then
+ if d1 is site(n1,_) then
if d2 is site(n2,_) then
- string_less(n1,n2).
-
-
-
+ string_less(n1,n2).
+
+
+
define One
update_site
(
Var(List(DispatcherInfo)) info_v
) =
- print("\n");
- with prefix = prompt(" Search for site to update: "),
- if find_sites(*info_v,prefix) is
+ print("\n");
+ with prefix = prompt(" Search for site to update: "),
+ if find_sites(*info_v,prefix) is
{
[ ] then print(" No site found.\n");
update_site(info_v),
- [h . t] then
+ [h . t] then
show_sites(qsort([h . t],compare),1);
with i1 = prompt(" Choose a site to update [1/.../"+(length(t)+1)+"]: "),
- if decimal_scan(i1) is
+ if decimal_scan(i1) is
{
failure then print(" *** Error: site number not recognized.\n");
update_site(info_v),
- success(ii1) then if nth(ii1-1,*info_v) is
+ success(ii1) then if nth(ii1-1,*info_v) is
{
failure then print(" *** Error: site number "+i1+" does not exist.\n");
update_site(info_v),
- success(site_info) then if site_info is site(name,old_port) then
+ success(site_info) then if site_info is site(name,old_port) then
update_site(info_v,name,old_port)
}
}
}.
-
+
define One
delete_site
@@ -4030,38 +4030,38 @@ define One
with answer = prompt(" Are you sure you want to delete site: '"+site_name+"' [Y/N] ? "),
if (answer = "Y" | answer = "y")
then info_v <- delete_info(*info_v,site_name)
- else print(" Site '"+site_name+"' not deleted.\n").
-
-
+ else print(" Site '"+site_name+"' not deleted.\n").
+
+
define One
delete_site
(
Var(List(DispatcherInfo)) info_v
) =
- print("\n");
- with prefix = prompt(" Search for site to delete: "),
- if find_sites(*info_v,prefix) is
+ print("\n");
+ with prefix = prompt(" Search for site to delete: "),
+ if find_sites(*info_v,prefix) is
{
[ ] then print(" No site found.\n");
delete_site(info_v),
- [h . t] then
+ [h . t] then
show_sites(qsort([h . t],compare),1);
with i1 = prompt(" Choose a site to delete [1/.../"+(length(t)+1)+"]: "),
- if decimal_scan(i1) is
+ if decimal_scan(i1) is
{
failure then print(" *** Error: site number not recognized.\n");
delete_site(info_v),
- success(ii1) then if nth(ii1-1,*info_v) is
+ success(ii1) then if nth(ii1-1,*info_v) is
{
failure then print(" *** Error: site number "+i1+" does not exist.\n");
delete_site(info_v),
- success(site_info) then if site_info is site(name,old_port) then
+ success(site_info) then if site_info is site(name,old_port) then
delete_site(info_v,name,old_port)
}
}
}.
-
-
+
+
define One
manager
(
@@ -4071,64 +4071,64 @@ define One
print("\n");
print(" --- Welcome to the Web Dispatcher Manager ---\n");
with l = length(*info_v),
- print(" "+l+" site"+(if l>1 then "s" else "")+" currently registred.\n");
+ print(" "+l+" site"+(if l>1 then "s" else "")+" currently registred.\n");
print(" [L] List registered sites.\n");
print(" [R] Register a new site.\n");
- print(" [U] Update a registred site.\n");
- print(" [D] Delete a registred site.\n");
- with propose_write_v = var((Bool)true),
- action = prompt(" Choose an action [L/R/U/D]: "),
+ print(" [U] Update a registred site.\n");
+ print(" [D] Delete a registred site.\n");
+ with propose_write_v = var((Bool)true),
+ action = prompt(" Choose an action [L/R/U/D]: "),
(if (action = "L" | action = "l") then (show_sites(*info_v,1); propose_write_v <- false) else
if (action = "R" | action = "r") then register_new_site(info_v) else
if (action = "U" | action = "u") then update_site(info_v) else
if (action = "D" | action = "d") then delete_site(info_v) else
print("Action not recognized.\n"));
- print("\n");
- if *propose_write_v then
+ print("\n");
+ if *propose_write_v then
with result = prompt(" Write modifications to data base [Y/N] ?"),
if (result = "Y" | result = "y")
- then if save(*info_v,file_path) is
+ then if save(*info_v,file_path) is
{
cannot_open_file then print(" File '"+file_path+"' not found.\n"),
write_error then print(" Error while writing file '"+file_path+"'.\n"),
ok then print(" Data base has been modified.\n")
}
else print(" Data base not modified.\n")
- else unique.
-
-
-
+ else unique.
+
+
+
global define One
manage_web_dispatcher
(
List(String) args
) =
- with info_v = var((List(DispatcherInfo))[]),
- with file_path = my_anubis_directory+"/web_sites/dispatcher.info",
+ with info_v = var((List(DispatcherInfo))[]),
+ with file_path = my_anubis_directory+"/web_sites/dispatcher.info",
if (RetrieveResult(List(DispatcherInfo)))retrieve(file_path) is
{
cannot_find_file then print("File '"+file_path+"' does not exist.\n");
with answer = prompt("Create it [Y/N] ? "),
if (answer = "Y" | answer = "y")
- then if save((List(DispatcherInfo))[],file_path) is
+ then if save((List(DispatcherInfo))[],file_path) is
{
- cannot_open_file then
+ cannot_open_file then
print("Cannot create file '"+file_path+"'.\n"),
write_error then
print("Error while creating file '"+file_path+"'.\n"),
ok then manager(info_v,file_path)
}
else unique,
- read_error then print("Error while reading file '"+file_path+"'.\n"),
- type_error then print("File '"+file_path+"' is corrupted.\n"),
- ok(info) then info_v <- info;
+ read_error then print("Error while reading file '"+file_path+"'.\n"),
+ type_error then print("File '"+file_path+"' is corrupted.\n"),
+ ok(info) then info_v <- info;
manager(info_v,file_path)
}.
-
-
-
-
-
+
+
+
+
+
public define (String, List(HTTP_header))
format
(
@@ -4138,7 +4138,7 @@ public define (String, List(HTTP_header))
{
http_continue then ("100 Continue", []),
http_switching_protocol then ("101 Switching Protocols", []),
-
+
http_ok then ("200 OK", []),
http_created then ("201 Created", []),
http_accepted then ("202 Accepted", []),
@@ -4146,7 +4146,7 @@ public define (String, List(HTTP_header))
http_no_content then ("204 No Content", []),
http_reset_content then ("205 Reset Content", []),
http_partial_content then ("206 Partial Content", []),
-
+
http_multiple_choices then ("300 Multiple Choices", []),
http_moved_permanently(loc) then ("301 Moved Permanently", [http_header("Location", loc)]),
http_moved_temporarily(loc) then ("302 Moved Temporarily", [http_header("Location", loc)]),
@@ -4154,7 +4154,7 @@ public define (String, List(HTTP_header))
http_not_modified then ("304 Not Modified", []),
http_use_proxy(loc) then ("305 Use Proxy", [http_header("Location", loc)]),
http_temporary_redirect(loc) then ("307 Temporary Redirect", [http_header("Location", loc)]),
-
+
http_bad_request then ("400 Bad Request", []),
http_unauthorized then ("401 Unauthorized", []),
http_payment_required then ("402 Payment Required", []),
@@ -4173,15 +4173,13 @@ public define (String, List(HTTP_header))
http_unsupported_media_type then ("415 Unsupported Media Type", []),
http_request_range_unsatisfiable then ("416 Requested range unsatisfiable", []),
http_expectation_failed then ("417 Expectation failed", []),
-
+
http_internal_server_error then ("500 Internal Server Error", []),
http_not_implemented then ("501 Not Implemented", []),
http_bad_gateway then ("502 Bad Gateway", []),
http_service_unavailable then ("503 Service Unavailable", []),
http_gateway_timeout then ("504 Gateway Time-out", []),
http_version_not_supported then ("505 HTTP Version not supported", []),
-
+
http_error(code, message) then (abs_to_decimal(code) + " " + message, [])
}.
-
-
diff --git a/web/jQuery/CXM_jq_checkbox.anubis b/web/jQuery/CXM_jq_checkbox.anubis
index 68a746e..5de5c79 100644
--- a/web/jQuery/CXM_jq_checkbox.anubis
+++ b/web/jQuery/CXM_jq_checkbox.anubis
@@ -1,9 +1,9 @@
/*
* Created by PyramIDE.
- * User: フランスのトトロ
+ * User: フランスのトトロ
* Date: 28/06/2015
* Time: 02:22
- * © Calexium
+ * © Calexium
*/
transmit calexium_lib/web/CXM_making_a_web_site.anubis
@@ -15,25 +15,25 @@ transmit tools/basis.anubis
transmit system/string.anubis
transmit tools/random.anubis
-define String
+define String
checkbox_init_script
(
String checkbox_id,
Bool initial_state
- ) =
+ ) =
"var checkbox = $('#" + checkbox_id + "');" +
- if initial_state then
+ if initial_state then
"checkbox.addClass('checked');"
else
"checkbox.addClass('unchecked');".
-define String
+define String
checkbox_onclick_script
(
String checkbox_id,
String on_script,
String off_script,
- ) =
+ ) =
"var checkbox = $('#" + checkbox_id + "');
checkbox.toggleClass('checked');
checkbox.toggleClass('unchecked');
@@ -42,14 +42,14 @@ define String
} else {
" + off_script + "
}".
-
+
public define HTML_Partial_Content
jq_img_checkbox
(
String checkbox_id,
Bool initial_state,
String image_url,
- String checkbox_label,
+ String checkbox_label,
String on_script,
String off_script,
Icon_orientation orientation
@@ -60,14 +60,14 @@ public define HTML_Partial_Content
],
jquery_button(jquery_img_button(image_url, checkbox_label, checkbox_id, jQuery_actioner(same, jqscript, checkbox_onclick_script(checkbox_id, on_script, off_script)), orientation))
).
-
+
public define HTML_Partial_Content
jq_img_checkbox
(
String checkbox_id,
Bool initial_state,
String image_url,
- String checkbox_label,
+ String checkbox_label,
String script,
Icon_orientation orientation
) =
diff --git a/web/jQuery/CXM_jq_radio.anubis b/web/jQuery/CXM_jq_radio.anubis
new file mode 100644
index 0000000..2b64837
--- /dev/null
+++ b/web/jQuery/CXM_jq_radio.anubis
@@ -0,0 +1,98 @@
+/*
+ * Created by PyramIDE.
+ * User: フランスのトトロ
+ * Date: 28/06/2015
+ * Time: 02:22
+ * © Calexium
+ */
+
+transmit calexium_lib/web/CXM_making_a_web_site.anubis
+transmit calexium_lib/web/CXM_jquery.anubis
+transmit calexium_lib/web/jQuery/CXM_jquery_button.anubis
+transmit calexium_lib/web/jQuery/CXM_jquery_icons.anubis
+transmit calexium_lib/web/js_tools.anubis
+transmit tools/basis.anubis
+transmit system/string.anubis
+transmit tools/random.anubis
+
+public type RadioGroup:
+ group(String name),
+ none.
+
+define String
+ radio_init_script
+ (
+ String radio_id,
+ RadioGroup radio_group,
+ Bool initial_state
+ ) =
+ "var radio = $('#" + radio_id + "');" +
+
+ if radio_group is
+ {
+ group(name) then
+ "radio.addClass('radio-group-" + name + "');",
+
+ none then
+ ""
+ } +
+
+ if initial_state then
+ "radio.addClass('checked');"
+ else
+ "radio.addClass('unchecked');".
+
+define String
+ radio_onclick_script
+ (
+ String radio_id,
+ RadioGroup radio_group,
+ String on_script
+ ) =
+ "
+ var radio = $('#" + radio_id + "');
+ if (radio.hasClass('checked')) return;
+ " +
+
+ if radio_group is
+ {
+ group(name) then
+ "$('.radio-group-" + name + "').addClass('unchecked');",
+
+ none then
+ ""
+ } +
+
+ "radio.removeClass('unchecked');
+ radio.addClass('checked');" + on_script.
+
+public define HTML_Partial_Content
+ jq_img_radio
+ (
+ String radio_id,
+ RadioGroup radio_group,
+ Bool initial_state,
+//Bool multi_select_with_shift,
+ String image_url,
+ String checkbox_label,
+ String on_script,
+ Icon_orientation orientation
+ ) =
+ partial_content(
+ [
+ js_inline(jquery_ready(radio_init_script(radio_id, radio_group, initial_state)))
+ ],
+ jquery_button(jquery_img_button(image_url, checkbox_label, radio_id, jQuery_actioner(same, jqscript, radio_onclick_script(radio_id, radio_group, on_script)), orientation))
+ ).
+
+public define HTML_Partial_Content
+ jq_img_radio
+ (
+ String radio_id,
+ Bool initial_state,
+ String image_url,
+ String checkbox_label,
+ String script,
+ Icon_orientation orientation
+ ) =
+ jq_img_radio(radio_id, none, initial_state, image_url, checkbox_label, script, orientation).
diff --git a/web/jQuery/CXM_jquery_button.anubis b/web/jQuery/CXM_jquery_button.anubis
index e6bbb33..418fddb 100644
--- a/web/jQuery/CXM_jquery_button.anubis
+++ b/web/jQuery/CXM_jquery_button.anubis
@@ -3,7 +3,7 @@
* User: Jeremy
* Date: 07/09/2012
* Time: 15:10
- *
+ *
*/
transmit calexium_lib/web/CXM_making_a_web_site.anubis
@@ -18,14 +18,14 @@ transmit tools/random.anubis
public type JQuery_button_type:
button,
reset,
- submit. //button with confirmation dialog
-
+ submit. //button with confirmation dialog
+
public define String
to_String
(
JQuery_button_type bt_type
)=
- if bt_type is
+ if bt_type is
{
button then "button",
reset then "reset",
@@ -33,7 +33,7 @@ public define String
}.
public type JQuery_button:
- jQuery_button( JQuery_button_type bt_type,
+ jQuery_button( JQuery_button_type bt_type,
String button_id, String button_name, String button_label, JQuery_Actioner actioner, JQuery_icon primary, JQuery_icon secondary // primary icon is for left icon, secondary icon is for right icon and button_id must set with an unique ID in order to have icons working
)
.
@@ -42,43 +42,12 @@ public define JQuery_button
jQuery_button
(
JQuery_button_type bt_type,
- String button_id,
- String button_name,
- String button_label,
+ String button_id,
+ String button_name,
+ String button_label,
JQuery_Actioner actioner
) =
jQuery_button(bt_type, button_id, button_name, button_label, actioner, jq_icon_none, jq_icon_none).
-
-define String
- jquery_button_get_onclick_action_window_options
- (
- List(Other_Window_Option) options,
- String formated_options
- )
- =
- //if there is already formated_options add a comma + space between the options
- with comma = if formated_options="" then "" else ", ",
-
- if options is
- {
- [ ] then formated_options,
- [ h . t ] then
- if h is
- {
- resizable then jquery_button_get_onclick_action_window_options(
- t, formated_options+ comma+ "resizable=yes" ),
-
- scrollbars then jquery_button_get_onclick_action_window_options(
- t, formated_options+ comma + "scrollbars=yes"),
-
- width(w) then jquery_button_get_onclick_action_window_options(
- t, formated_options+ comma + "width="+w),
-
- height(h) then jquery_button_get_onclick_action_window_options(
- t, formated_options+ comma + "height="+h)
- }
- }
- .
define HTML_Head_Tag
jquery_button_get_onclick_action
@@ -87,31 +56,8 @@ define HTML_Head_Tag
JQuery_Actioner actioner
)
=
- if actioner is jQuery_actioner(target, act_type, str) then
- if target is
- {
- same then
- if act_type is
- {
- jqform then js_inline(jquery_ready("$('#"+button_id+"').click(function(){$('#"+str+"').submit();});")),
- jqlink then js_inline(jquery_ready("$('#"+button_id+"').click(function(){document.location='/?a="+str+"';});")),
- jqscript then js_inline(jquery_ready("$('#"+button_id+"').click(function(){"+str+"});"))
- },
- same(label) then
- if act_type is
- {
- jqform then js_inline(jquery_ready("$('#"+button_id+"').click(function(){$('#"+str+"').submit();});")),
- jqlink then js_inline(jquery_ready("$('#"+button_id+"').click(function(){document.location='/?a="+str+"';});")),
- jqscript then js_inline(jquery_ready("$('#"+button_id+"').click(function(){"+str+"});"))
- },
- other(window_name, window_options) then
- if act_type is
- {
- jqform then js_inline(jquery_ready("$('#"+button_id+"').click(function(){$('#"+str+"').submit();});")), //il s'agit d'une soumision d'un formulaire => cela se fait uniquement dans la même fenêtre !
- jqlink then js_inline(jquery_ready("$('#"+button_id+"').click(function(){window.open('"+str+"', '"+window_name+"', '"+jquery_button_get_onclick_action_window_options(window_options, "")+"');});")),
- jqscript then js_inline(jquery_ready("$('#"+button_id+"').click(function(){"+str+"});"))
- }
- }
+ with js_action = jquery_button_get_onclick_action(actioner),
+ js_inline(jquery_ready("$('#"+button_id+"').click(function(e){" + js_action + "});"))
.
define HTML_Partial_Content
@@ -138,7 +84,7 @@ define HTML_Partial_Content
[ onclick_action . html_tags ],
[
literal(""+label+" ")
-
+
.
html_buttons
]
@@ -265,7 +211,7 @@ public define HTML_Partial_Content
literal(""+label+" ")
)
.
-
+
public type Icon_orientation:
top,
bottom,
@@ -274,7 +220,7 @@ public type Icon_orientation:
public type JQuery_img_button:
jquery_img_button(String img_url, String label_text, String id, JQuery_Actioner jq_action, Icon_orientation orientation).
-
+
public define HTML_Off_Form
jquery_button_html
(
@@ -292,7 +238,7 @@ public define HTML_Off_Form
}).
-
+
public define HTML_Partial_Content
jquery_button
(
@@ -309,9 +255,9 @@ public define HTML_Partial_Content
css(css_file("css/cxm/cxm_button.css")),
js_inline(jquery_ready("$('.jq_img_btn').button();"))
],
- jquery_button_html(button, button_id_str))
+ jquery_button_html(button, button_id_str))
.
-
+
public define HTML_Partial_Content
jq_button_confirm
(
@@ -320,10 +266,10 @@ public define HTML_Partial_Content
String dlg_text,
String dlg_ok,
String dlg_cancel,
- String url,
- List((String, String)) extra_ops
+ //String url,
+ JQuery_Actioner actioner
)=
- with uid = generate_random_string(20),
+ with uid = generate_random_string(10),
js_confirm_dialog_data_obj = "CalexiumToolBox.data.confirm_dialog[" + to_js_string(uid) + "]",
js_data = js_confirm_dialog_data_obj + " = {
@@ -333,26 +279,12 @@ public define HTML_Partial_Content
cancel: " + to_js_string(dlg_cancel) + "};",
button_name = uid,
- actioner = jQuery_actioner(same, jqscript, "CalexiumToolBox.make_confirm_dialog(this, " + to_js_string("/?a=" + url + format_extra_operands(extra_ops))+");"),
+ actioner = jQuery_actioner(same, jqscript, "CalexiumToolBox.make_confirm_dialog(this, " /*+ to_js_string("/?a=" + url + format_extra_operands(extra_ops))+*/ + to_js_string(jquery_button_get_onclick_action(actioner)) + ");"),
partial_content([js(js_file("js/cxm/cxm.js")), js_inline(jquery_ready(js_data))],sequence([
- partial(jquery_button(jQuery_button(button, uid, button_name, button_label, actioner, jq_icon_none, jq_icon_none)))
- ])).
+ partial(jquery_button(jQuery_button(button, uid, button_name, button_label, actioner, jq_icon_none, jq_icon_none)))
+ ])).
-
-
-public define HTML_Partial_Content
- jq_button_confirm
- (
- String button_label,
- String dlg_title,
- String dlg_text,
- String dlg_ok,
- String dlg_cancel,
- String url
- )=
- jq_button_confirm(button_label, dlg_title, dlg_text, dlg_ok, dlg_cancel, url, []).
-
public define HTML_Partial_Content
img_submit_button
(
diff --git a/web/jQuery/ui-addon/datetimepicker.anubis b/web/jQuery/ui-addon/datetimepicker.anubis
index 98d0012..7d42419 100644
--- a/web/jQuery/ui-addon/datetimepicker.anubis
+++ b/web/jQuery/ui-addon/datetimepicker.anubis
@@ -1,9 +1,9 @@
/*
* Created by PyramIDE.
- * User: フランスのトトロ
+ * User: フランスのトトロ
* Date: 16/11/2014
* Time: 12:14
- * © Calexium
+ * © Calexium
*/
read calexium_lib/web/CXM_making_a_web_site.anubis
@@ -13,7 +13,7 @@ read calexium_lib/web/CXM_jquery.anubis
public define List(HTML_Head_Tag)
jq_ui_datetimepicker_init
=
- [
+ [
css(css_file("/css/jquery/ui/datetimepicker.css")),
js(js_file("js/jquery/ui/jquery-ui-timepicker-addon.js")),
js_inline(jquery_ready("// Datepicker\n
@@ -23,7 +23,7 @@ read calexium_lib/web/CXM_jquery.anubis
]
.
-
+
/* Initialize jQuery datetimepicker */
public define List(HTML_Head_Tag)
jq_datetimepicker_init
@@ -31,7 +31,7 @@ public define List(HTML_Head_Tag)
String lang
)
=
- [
+ [
css(css_file("js/jquery/plug-in/datetimepicker/datetimepicker.css")),
js(js_file("js/jquery/plug-in/datetimepicker/datetimepicker.js")),
js_inline(jquery_ready("// Datetimepicker\n
diff --git a/web/widgets/button.anubis b/web/widgets/button.anubis
index da894a2..1bda49e 100644
--- a/web/widgets/button.anubis
+++ b/web/widgets/button.anubis
@@ -3,7 +3,7 @@
* User: フランスのトトロ & Juju
* Date: 18/04/2015
* Time: 20:46
- * © Calexium
+ * © Calexium
*/
read calexium_lib/web/CXM_making_a_web_site.anubis
@@ -33,7 +33,7 @@ public define HTML_Partial_Content
String url
)=
img_button_flink(img_path, same, url).
-
+
public define HTML_Partial_Content
img_button
(
@@ -43,7 +43,7 @@ public define HTML_Partial_Content
List((String, String)) extra_ops
)=
partial_content(actioner(same, same, img_link(img_path, ""), url, extra_ops)).
-
+
public define HTML_Partial_Content
img_button
(
@@ -60,7 +60,7 @@ public define HTML_Partial_Content
List((String, String)) extra_ops
)=
img_button(img_path, same, url, extra_ops).
-
+
define inline String
img_button_confirm_js
(
@@ -70,10 +70,10 @@ define inline String
String dlg_ok,
String dlg_cancel
) =
- "CalexiumToolBox.data.confirm_dialog[" + to_js_string(uid) + "] = " +
- "{ title: " + to_js_string(dlg_title) +
+ "CalexiumToolBox.data.confirm_dialog[" + to_js_string(uid) + "] = " +
+ "{ title: " + to_js_string(dlg_title) +
", text: " + to_js_string(dlg_text) +
- ", ok : " + to_js_string(dlg_ok) +
+ ", ok : " + to_js_string(dlg_ok) +
", cancel: " + to_js_string(dlg_cancel) +
"};".
@@ -82,10 +82,9 @@ define List(CoreAttrs)
(
List(CoreAttrs) core_attrs,
String uid,
- String url,
- List((String, String)) extra_ops
+ JQuery_Actioner actioner
) =
- core_attrs + [id(uid), style("cursor: pointer"), event(onclick, "CalexiumToolBox.make_confirm_dialog(this, " + to_js_string("/?a=" + url + format_extra_operands(extra_ops))+");")].
+ core_attrs + [id(uid), style("cursor: pointer"), event(onclick, "CalexiumToolBox.make_confirm_dialog(this, "/* + to_js_string("/?a=" + url + format_extra_operands(extra_ops))+*/+ to_js_string(jquery_button_get_onclick_action(actioner)) +");")].
public define HTML_Partial_Content
img_button_confirm
@@ -96,13 +95,12 @@ public define HTML_Partial_Content
String dlg_text,
String dlg_ok,
String dlg_cancel,
- String url,
- List((String, String)) extra_ops
+ JQuery_Actioner actioner
)=
with uid = generate_random_string(20),
partial_content([js(js_file("js/cxm/cxm.js")), js_inline(jquery_ready(img_button_confirm_js(uid, dlg_title, dlg_text, dlg_ok, dlg_cancel)))],
- image(img_button_confirm_image_attrs(core_attrs, uid, url, extra_ops), img_path, "")).
+ image(img_button_confirm_image_attrs(core_attrs, uid, actioner), img_path, "")).
public define HTML_Partial_Content
img_button_confirm
@@ -112,35 +110,9 @@ public define HTML_Partial_Content
String dlg_text,
String dlg_ok,
String dlg_cancel,
- String url
- )=
- img_button_confirm(img_path, [], dlg_title, dlg_text, dlg_ok, dlg_cancel, url, []).
-
-public define HTML_Partial_Content
- img_button_confirm
- (
- String img_path,
- List(CoreAttrs) core_attrs,
- String dlg_title,
- String dlg_text,
- String dlg_ok,
- String dlg_cancel,
- String url
- )=
- img_button_confirm(img_path, core_attrs, dlg_title, dlg_text, dlg_ok, dlg_cancel, url, []).
-
-public define HTML_Partial_Content
- img_button_confirm
- (
- String img_path,
- String dlg_title,
- String dlg_text,
- String dlg_ok,
- String dlg_cancel,
- String url,
- List((String, String)) extra_ops
+ JQuery_Actioner actioner
)=
- img_button_confirm(img_path, [], dlg_title, dlg_text, dlg_ok, dlg_cancel, url, extra_ops).
+ img_button_confirm(img_path, [], dlg_title, dlg_text, dlg_ok, dlg_cancel, actioner).
public define String
img_button_confirm
@@ -151,13 +123,12 @@ public define String
String dlg_text,
String dlg_ok,
String dlg_cancel,
- String url,
- List((String, String)) extra_ops
+ JQuery_Actioner actioner
) =
with uid = generate_random_string(20),
- "" +
- " ".
+ "" +
+ " ".
public define String
img_button_confirm
@@ -167,33 +138,6 @@ public define String
String dlg_text,
String dlg_ok,
String dlg_cancel,
- String url
- )=
- img_button_confirm(img_path, [], dlg_title, dlg_text, dlg_ok, dlg_cancel, url, []).
-
-public define String
- img_button_confirm
- (
- String img_path,
- List(CoreAttrs) core_attrs,
- String dlg_title,
- String dlg_text,
- String dlg_ok,
- String dlg_cancel,
- String url
- )=
- img_button_confirm(img_path, core_attrs, dlg_title, dlg_text, dlg_ok, dlg_cancel, url, []).
-
-public define String
- img_button_confirm
- (
- String img_path,
- String dlg_title,
- String dlg_text,
- String dlg_ok,
- String dlg_cancel,
- String url,
- List((String, String)) extra_ops
+ JQuery_Actioner actioner
)=
- img_button_confirm(img_path, [], dlg_title, dlg_text, dlg_ok, dlg_cancel, url, extra_ops).
-
+ img_button_confirm(img_path, [], dlg_title, dlg_text, dlg_ok, dlg_cancel, actioner).
--
libgit2 0.21.4