*Project* Anubis *Title* Making interactive Web sites. *Copyright* Copyright (c) Alain Prouté 2004-2005. Copyright (c) Calexium 2007. *Authors* Alain Prouté David René *Revised* May 2007 *Overview* In this file we propose simple tools for making well structured interactive and secured web sites. ----------------------------------- Table of Contents --------------------------------- * (1) Structure of a web site. ** (1.1) Three sorts of data. ** (1.2) How requests are handled. ** (1.3) What web pages are made of. ** (1.4) Actions. ** (1.5) States. * (2) Carrying on. ** (2.1) Describing your web sites. ** (2.2) Directories on the server's disk. ** (2.3) Starting your web sites. * (3) The HTML interface. ** (3.1) Types used by the HTML interface. ** (3.2) ``in form'' versus ``off form''. ** (3.3) Defining your own style. ** (3.4) Actioners and forms. ** (3.5) Local popup. --------------------------------------------------------------------------------------- read tools/basis.anubis read system/logger.anubis read CXM_common.anubis read CXM_multihost_http_server.anubis read CXM_mime.anubis * (1) Structure of a web site. First of all we need to explain what a web site should be made of. Ideally, the visitor (also called the 'client') should see the web site working as any other interactive computer software. So, it should be clear that a 'session' (i.e. a visit to the web site, including the consultation of several pages) is some kind of conversation between the visitor and the web site, and that the web site should maintain a 'current state' of this conversation. At each new request (click) from the visitor, this state must be updated. This whole conversation is called a 'session' and should not be confused with a single request. ** (1.1) Three sorts of data. All the data needed for putting a web site at work may be dispatched into three categories: 1. Constant data (data that never change). These data may be hard coded into the Anubis source files of the web site. 2. Permanent data (data which always exist independantly of the users connected to the web site). These data are normally recorded into data bases. 3. Session data (data which depend on a particular visitor and which exist only during the time he visits the web site). These data are stored into so-called 'states'. It is important to determine which data belongs to which category. This is part of your design decisions. ** (1.2) How requests are handled. We want to separate the following two functionalities (which are used at each request (click) during a single session): - computing the new state from the previous state and from the client request, and updating the data base, - computing the page to be sent to the client from the new current state and from the informations in the data base. The next picture shows the structure we have in mind: request +---------+ HTML page (with a hidden state name) .-------------------| client |<--------------. | .-----------------| | | | | previous state +---------+ | | | name (if any) | | | | client side ............................................................................ | | | server side | | | | | .-------------------. | | | | previous state | | V V V | | +---------------+ +---------------+ +--------------+ | compute state | | server's disk | | compute page | +---------------+ +---------------+ +--------------+ ^ | | ^ ^ ^ ^ ^ | | | | | | | | | | `--------------------+--------------------' | | | | new state | | | read | `------------------------+--------------------' | write | new state name | update V | +-----------+ | | data base |--------------------------------------------' +-----------+ read only When the client begins a session, there is no previous state. In this case, a default 'initial state' is used instead. The data base may be updated by 'compute state' box, but should not be update by the 'compute page' box. The 'compute page' box should be allowed only to read the data base. In this file, all the above stuff is defined, except the 'compute state' and 'compute page' boxes. You just have to provide the function for computing a new state (compute state) and the function for computing the page (compute page) from the new state. You don't have to worry about state names, saving and retrieving states and the like. ** (1.3) What web pages are made of. What the client can see in his browser's window may be called a 'page'. Within a page, we have several sorts of components: - 'local' components, i.e. all components which do not open a connection, like texts, images, etc... possibly using JavaScript programmation, - 'actioners', which, when clicked upon, open a connection with our web site; they may appear as links or buttons, etc... - 'foreign links', which when clicked upon, open a connection with another web site (or ours eventually). Of course, what an actioner does is just ask our web site to perform an action. To that end, the actioner essentially sends the name of the action to be performed. However, it may be necessary to provide additional informations which may be seen as 'operands' of the action. In order to attach operands to an action, HTML provides the notion of 'form'. Indeed, a form contains essentially a set of input fields into which the client may put values for the required operands of the action, and a submit button, which is the actioner itself. Notice that a single form may contain several submit buttons, which simply means that there are several distincts actions taking the same set of operands. Restrictions must be put on the use of all theses gadgets. Indeed, for example, putting a form within another form is officially meaningless in HTML, and the client's browser may be seriously disturbed by this. In this file, we propose an interface to the HTML language, which forbids such meaningless things, simply by imposing a strict typing of HTML concepts. Each web site may be accessible through two communication channels: - a non secured channel (HTTP), - a secured channel (HTTPS). Nevertheless, the whole thing should be considered as a single web site. For example, you may have a secured page, obtained through HTTPS, containing public images obtained through HTTP. An actioner in a non secured page may open a secured connection, and conversely. Summarizing, a web page is made of local elements, foreign links and actioners. Actioners receive operands from forms, and they also choose to communicate through the non secured or through the secured channel. +-------------------+ | page | | | +---------------+ | +--------------+ | | next page | | | form | | | (non secured) | | | +----------+ | | HTTP | | | | | actioner |---------------------------->| | | | +----------+ | | +---------------+ | | | | | | +----------+ | | +---------------+ | | | actioner |---------------------------->| next page | | | +----------+ | | HTTPS | (secured) | | | | | | | | +--------------+ | | | | | +---------------+ | | +-------------------+ Notice that actioners need no be necessarily put into forms. In that case, they work as ordinary links, but they still may receive operands as we shall see. ** (1.4) Actions. The client opens a new connection with our web site whenever he clicks on an actioner. The result is that a request is sent, essentially made of a list of 'web arguments'. Each web argument is a pair (name,value). One of these web arguments, the 'action' web argument (whose name is "a"), determines the action to be performed. The other web arguments (not including "s", used to identify the state) are the operands for this action. Hence, the 'compute state' box in the picture above, splits naturally into as many sub-boxes as there are actions. For this reason, we define the following type for representing actions (where '$State' is the type representing session informations): public type Web_Action($SessionTicket, $State): http_action (String name, // name of action (Maybe($State)) -> Bool allow, // true if action allowed (HTTP_Info http_info, List(Web_arg) web_args, // actually only 'operands' web arguments Maybe($State) state) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) do_it), https_action (String name, // name of action (Maybe($State)) -> Bool allow, // true if action allowed (HTTP_Info http_info, List(Web_arg) web_args, // actually only 'operands' web arguments Maybe($State) state) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) do_it), http_https_action (String name, (Maybe($State)) -> Bool allow, // true if action allowed (HTTP_Info http_info, List(Web_arg) web_args, Maybe($State) state) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) do_it). 'http_action's are executed only under HTTP, and 'https_action's are executed only under HTTPS. 'http_https_action's may be executed under both types of connections. Each action has a name, which is used to identify the action. Each action also has a function 'allow' whose job is to verify that the action is allowed in the current state, and a function 'do_it' for performing the action. The function 'do_it' receives a lot of informations: - 'HTTP informations': - the IP address of the client, - the URI requested by the client (after redirection), - the list of HTTP headers generated by the client's browser, - the list of web arguments sent by the client (except "s" and "a"), - the previous state (or the 'initial' or 'ticket expired' state if no previous state can be found). In most cases, HTTP informations are not used. This is the reason why they are gathered for simplicity into a unique datum of type 'HTTP_Info'. // For your convenience, we introduce the following simpler variants: // //public define Web_Action($State) // http_action // ( // String name, // $State -> Bool allow, // (List(Web_arg),$State) -> $State do_it // ) = // http_action(name, // (Maybe($State) ms) |-> if ms is // { // failure then true, // success(s) then allow(s) // }, // (HTTP_Info h, List(Web_arg) l, Maybe($State) s) |-> if s is // { // failure then (failure, []), // success(s2) then (success(do_it(l,s2)), []) // }). // //public define Web_Action($State) // https_action // ( // String name, // $State -> Bool allow, // (List(Web_arg),$State) -> $State do_it // ) = // https_action(name, // (Maybe($State) ms) |-> if ms is // { // failure then true, // success(s) then allow(s) // }, // (HTTP_Info h, List(Web_arg) l, Maybe($State) s) |-> if s is // { // failure then (failure, []), // success(s2) then (success(do_it(l,s2)), []) // }). // //public define Web_Action($State) // http_https_action // ( // String name, // $State -> Bool allow, // (List(Web_arg),$State) -> $State do_it // ) = // http_https_action(name, // (Maybe($State) ms) |-> if ms is // { // failure then true, // success(s) then allow(s) // }, // (HTTP_Info h, List(Web_arg) l, Maybe($State) s) |-> if s is // { // failure then (failure, []), // success(s2) then (success(do_it(l,s2)), []) // }). When you define your web site, you must provide the list of all the actions of the site. When a new state has been computed, a graphical representation of this state must be sent to the client. To that end, you must provide a function (named below 'compute_page') of type: $State -> HTML_Page where the type 'HTML_Page' (defined below in this file) abstractly represents HTML pages. public type HTML_Page:... It should be clear that states and pages are deeply linked together. Indeed, we really understand the page shown to the client as a representation of the current state of the conversation between the client and the web site, but also containing informations taken from the data bases. ** (1.5) States. Now, we explain how you can define the type (say 'State') to be used as an instance of the type parameter '$State'. The following is just a suggestion. Each state determines a page (since 'compute_page' computes a page from a state). However, some components of the state may be independant of the page. It may be the case for example for the indication of the natural language used by the client. Hence, a state should be made of (at least) two parts: - informations which are the same for all pages, - informations which are particular to each page. For example, you could define: type Page: // one alternative per page, with particular informations login(...), // in the components main_page(...), ...etc... Now, the type 'State' could be defined as follows: type State: state(Language, // informations valid for all pages ..., Page). // informations particular to a page However, if you are making a secured web site within which clients should be identified (by id and password), it may be a good idea to have two sorts of states, one for non identified clients and one for identified clients. In this case, define the type 'State' as follows (this is just a suggestion): type State: non_identified(Language), identified(String id, Language, Page). When a request arrives, check if the previous state is 'identified(...)' or 'non_identified(...)', and don't provide access to certain pages to non identified clients. This is required for security. Some more words on security. If your site needs to identify clients, define the initial state as 'non_identified(...)'. Construct a 'login' page, and check the id and password of the client. If the id and password are correct, then change the state of the client to 'identified(...)'. No other action should be able to do that. Now, be confident that clients cannot forge states. The only information they have is the name of a state, not the state itself which is never sent over the network, but only stored on the server's disk. The name of the state is constructed using strong cryptographical methods (sha1). If everything (since the 'login' page) is performed under HTTPS, even state names cannot be seen by a third party. So, if the system retrieves a previous state of the form 'identified(...)', you can be confident that your client is well identified, and you can send him confidential informations. States have a limited life time. It may happen that a client clicks on a button at a time its state is out of date. In this case, this system considers that the new state is a special state named 'ticket expired'. You must provide a function producing this state when you describe your web site. The page corresponding to this state must just inform the client that he/she waited a too long time before clicking on a button, and has to restart (a new conversation) from the begining. * (2) Carrying on. ** (2.1) Describing your web sites. Before you may start your web site, you must describe it, i.e. produce a datum of the opaque type 'Web_Site'. public type Web_Site:... Producing such a datum may be performed by: public define Web_Site make_web_site_description ( List(String) common_names, // for example: ["www.our-business.com", // "192.168.0.1"] // the second one is just for testing String site_directory, // where 'public' and other directories are // located (should NOT end with '/') One -> One init, (HTTP_Info) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) initial_state, ($State expired, HTTP_Info, List(Web_arg), Bool is_https) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) ticket_expired_state, (HTTP_Info, List(Web_arg), Bool is_https) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) ticket_lost_state, List(Web_Action($SessionTicket, $State)) actions, (Maybe($SessionTicket), Maybe($State)) -> HTML_Page compute_page, Int32 timeout, // seconds (todo: minutes) List(Redirection) redirections, String charset, List(String) journal_extensions, List(String) journal_headers, String authorization_secret, List(MIME) known_mime_types, (String action_name, List(Web_arg) args)-> One before_send_file ). Explanations: 'common_names' is the list of names of the site (the name the browser must send as the value of the 'Host' HTTP header in order to access the site must be in that list). Such a name generally looks like this: www.somewhere.com If you are using HTTPS, you also have an 'X.509 SSL server certificate'. The name of the site must be exactly the same as the name on the certificate (which is precisely called the 'common name' in the X.509 jargon). If the two names do not match, the site will still work, but the transaction will not be transparent to the client. His browser will complain that the name of the certificate does not match the name of the site, and he will have to accept the certificate manually. 'site_directory' is the absolute path to the directory where the files needed by the site are located. Usually this directory looks like: my_anubis/web_sites/www.somewhere.com However, this information is not computed from 'common_name', so that you can change the common name (for example temporarily, for networking reasons) without loosing access to the files. 'ticket_expired_state(expired_state,http_info,lwa,is_https)' must produce the state whose graphical representation is a page explaining to the user that its 'ticket' (or 'session information') has expired, and that he/she must close all popup windows and start a new session. The arguments of the function contain the previous (expired) state and all current informations concerning the user. This arguments may be useful for example for producing the expiration message in the language chosen by the user. You can also (and this may be much smarter) send a 'ticket prolongation page' (including a new login for example), and resume the same conversation, since you have all the pertinent informations at hand. In the case the ticket is definitely lost, the second fonction 'ticket_lost_state' is used. Notice that despite the fact that the parameter $State is involved in the arguments of the above function, the type 'Web_Site' does not depend on this parameter. This allows to produce lists of web site descriptions, where each description may be constructed with a different instance of $State. This is required because distinct sites must have distinct types of session informations. This is made possible by the fact that the type is obscure, and the constructor replaced by a function which assembles 'ticket_expired_state', ticket_lost_state', 'actions' and 'compute_page' into a single entity not depending on $State. You should have a look to the private part of this file if you want more precisions about this programming technique. '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... 'before_send_file' is a function which is executed just before the HTTP server sends a file. It gets an action name and the web arguments received with the request for that file. Notice that this action name and these web arguments may be put into a 'private_download' element, and will come back to the server at the time of the download. ** (2.2) Directories on the server's disk. The description of you site contains the name of the directory within which the required files are located. This may be for example: my_anubis/web_sites/www.our-business.com/ This is called the 'site directory' (for the given site). Within the site directory, the following directories are created by this program: states public journal private_download upload_temporary The directory 'states' is used for storing states (session informations). Out of date states are automatically removed after some time. The tree rooted at 'public' contains files that the server is allowed to send to the clients. For security reasons, the server never sends a file which is not within the tree whose root is this 'public' directory (except for the 'private download' mecanism; see 'web/multihost_http_server.anubis'). Also, the MIME type (see 'web/mime.anubis') must have been recognized before the file may be sent. The directory 'journal' contains the jounal files. The roles of the remaining directories 'private_download' and 'upload_temporary' is explained in 'multihost_http_server.anubis', where you will also find further informations on 'public' and 'journal'. ** (2.3) Web servers parameters. The web servers have several parameters useful for administration. They are described as follows: public type WebServersParameters: wsparms(Var(Bool) shutdown_required, ** (2.4) Starting your web sites. When you have described all your web sites (you may want to have several web sites, and they are distinguished by their 'common name'), you may start them all together using 'start_web_sites' below. This function returns a result of the following type: public type Start_Web_Sites_Result: cannot_bind_to_port(Int32), cannot_bind_to_port(Int32,Int32), ok(Server http_server, Server https_server). Indeed, it may happen that the system cannot bind (begin to listen) to one of the two ports (or to both). The main reason is that another server is already listening on that port. Another reason may be that 'anbexec' has not been correctly installed, i.e. that the 's' bit has not been set for 'user' and 'group' (there is not such problem under Windows). Also notice that the Linux kernel may need a rather long time (up to several minutes) before liberating a listening port. Now, if the system can bind to the two ports, the pair of the two servers is returned. Two tools are useful for manipulating servers: shutdown of type Server -> One is_down of type Server -> Bool They are defined in 'predefined.anubis' (together with the type 'Server'). public define Start_Web_Sites_Result start_web_sites ( Int32 ip_address, // the IP address shared by the web sites Int32 http_port, // usually: 80 Int32 https_port, // usually: 443 String ssl_certificate_common_name, List(Web_Site) web_sites, // web sites to be started Var(Bool) shutdown_required ). 'ip_address' is the IP address on which the two servers listen. If you put 0, the servers listen on all the IP addresses of the machine. This may be useful if the machine has several network interfaces. 'ssl_certificate_common_name' is the common name of the SSL certificate that 'anbexec' loads when it starts. One instance of 'anbexec' cannot handle more than one SSL server certificate. This is due to a problem of conception of SSL itself. See the book 'SSL and TLS' by Eric Rescorla (at Addison Wesley) for more explanations. Notice that the number of servers is always 2, regardless of the number of web sites you are starting. The dynamic variable 'shutdown_required' may be used to control the shutdown of the two servers from within the web site (typically the administration part). The servers will shutdown as soon as this variable contains 'true'. So you must provide a variable containing 'false' otherwise your servers will not run. You may also use the primitive 'must_restart' (see 'predefined.anubis') to control the restarting of your servers. * (3) The HTML interface. We propose an interface to dynamic HTML. Dynamic HTML includes HTML, and a combination of CSS (Cascading Style Sheet) and JavaScript techniques for making HTML elements more reactive and attractive on the client side. ** (3.1) Types used by the HTML interface. For easy reference, we gather below the definitions of all the types used by the HTML interface, and we comment them immediately. public type HTML_Size: absolute(Int32), // in pixels percentage(Int32). public type Text_Option: size(Int32), // size of character font to use font(String), // name of character font to use (such as "helvetica",...) color(RGB), // color to be used for characters italic, oblique, small_capitals, bold, underlined, left_justified, right_justified, justified, // justified on both sides line_through, nowrap, class(String), //CSS class id(String). A list of 'Text_Option' must be given with each text you want to put in your page. This indicate the way of reading text. public type Reading_Way: ltr, //the text is readable from "Left To Right" like english rtl. //the text is readable from "Right To Left" like arabic // Following types are define to prohib any arguments order error /** * String value for 'id' attribut */ public type HtmlId: htmlId(String id). /** * String value for 'class' attribut */ public type HtmlClass: htmlClass(String class). /** * Name used in arguments list when form is submitted. */ public type WebArgName: wan(String name). /** * Value passed to arguments list when form is submitted (for RadioButton and CheckBox). */ public type WebArgValue: wav(String value). /** * Initial value of an input field. */ public type InitialValue: init(String value). public define Printable_tree [HtmlId x . Printable_tree y] = str_pt(x.id, y). public define Printable_tree [HtmlClass x . Printable_tree y] = str_pt(x.class, y). public define Printable_tree [WebArgName x . Printable_tree y] = str_pt(x.name, y). public define Printable_tree [WebArgValue x . Printable_tree y] = str_pt(x.value, y). public define Printable_tree [InitialValue x . Printable_tree y] = str_pt(x.value, y). public type HtmlEvents: // Form element events onchange, onsubmit, onreset, onselect, onblur, onfocus, // Keyboard events (Not valid in base, bdo, br, frame, frameset, head, html, iframe, meta, param, script, style, and title elements.) onkeydown, onkeypress, onkeyup, // Mouse events (Not valid in base, bdo, br, frame, frameset, head, html, iframe, meta, param, script, style, and title elements.) onclick, ondblclick, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup. public type CoreAttrs: id (String), class (String), style (String), tooltip (String), lang (String), dir (Reading_Way), accesskey (Word8), tabindex (Int32), attr (String, String), event (HtmlEvents, String). public type InputAttrs: class (String), style (String), tooltip (String), lang (String), dir (Reading_Way), accesskey (Word8), tabindex (Int32), attr (String, String), event (HtmlEvents, String). public type I18n: lang (String), dir (Reading_Way). //public type DIV_Option: // id (String), // class (String), // style (String), // tooltip (String), // lang (String), // dir (Reading_Way), // attr (String, String), // event (HtmlEvents, String). // A list of 'DIV_Option' must be given with each DIV you want to put in your page. define String event_name ( HtmlEvents e ) = if e is { // Form element events onchange then "onchange", onsubmit then "onsubmit", onreset then "onreset", onselect then "onselect", onblur then "onblur", onfocus then "onfocus", // Keyboard events onkeydown then "onkeydown", onkeypress then "onkeypress", onkeyup then "onkeyup", // Mouse events onclick then "onclick", ondblclick then "ondblclick", onmousedown then "onmousedown", onmousemove then "onmousemove", onmouseout then "onmouseout", onmouseover then "onmouseover", onmouseup then "onmouseup" }. public type Table_Option: background_color(RGB), // applied to all cells in the table background_image(String url), border(Int32 width_of_outer_edge, // if not present, all values are 0 Int32 width_of_top_of_relief, Int32 width_of_inner_edge, RGB border_color), width(Int32), // sets a minimal width for the table percentage_width(Int32). public define Table_Option nude = border(0,0,0,rgb(0,0,0)). A list of 'Table_Option' must be given with each table. public type BackgroundOption: repeat, // repeat the background in both directions repeat_horizontal, // repeat the background only horizontally repeat_vertical, // repeat the background only verticall no_repeat, // don't repeat the background center. public type Cell_Option: core_attrs(List(CoreAttrs)), left, // put the content of the cell on the left h_center, // center the content of the cell horizontally right, // put the content of the cell on the right top, // put the content of the cell upwards v_center, // center the content of tye cell vertically, bottom, // put the content of the cell downwards base_line, // align the content vertically according to base lines background_color(RGB), background_image(String url, BackgroundOption), width(Int32), // sets a minimal width for the cell percentage_width(Int32), height(Int32), // sets a minimal height for the cell columns(Int32), // lets the cell span over several columns rows(Int32), // lets the cell span over several rows nowrap. // do not allow text wrapping within the cell A list of 'Cell_Option' must be given with each cell and each row in a table. Options given with a row apply to all the cells in the row, but are superseded by options given with cells, which apply only to the cell they are given with. public type HTML_Cell($T): cell(List(Cell_Option) options, $T content). public type HTML_Header_Cell($T): header_cell(List(Cell_Option) options, $T content). The parameter $T is later instantiated either to 'HTML_In_Form' or to 'HTML_Off_Form', depending on where you put your table (within a form or not within a form). For your convenience, we define the following particular case: public define HTML_Cell($T) cell ( $T content ) = cell([],content). public define HTML_Header_Cell($T) header_cell ( $T content ) = header_cell([],content). public type HTML_Row($T): row(List(Cell_Option) options, List(HTML_Cell($T)) cells). public type HTML_Header_Row($T): empty, header_row(List(Cell_Option) options, List(HTML_Header_Cell($T)) cells). Same remark as for 'HTML_Cell($T)'. We define several convenience functions: public define HTML_Row($T) row ( List(HTML_Cell($T)) cells ) = row([],cells). public define HTML_Row($T) row ( HTML_Cell($T) cell ) = row([],[cell]). public define HTML_Header_Row($T) header_row ( List(HTML_Header_Cell($T)) cells ) = header_row([],cells). public define HTML_Header_Row($T) header_row ( HTML_Header_Cell($T) cell ) = header_row([],[cell]). public type Actioner_Connection: same, // use same type of connection as current page http, // use non secured connection https. // use secured connection public type Other_Window_Option: resizable, // the new window may be resized by the client scrollbars, // the new window has scrollbars width(Int32), // the new window has the specified width height(Int32). // the new window has the specified height public type Actioner_Target: same, same (String label), other(String window_name, List(Other_Window_Option)). public type Actioner_Aspect: link (List(Text_Option),String text), // hypertext link push_button (List(CoreAttrs),String text), submit (List(CoreAttrs),String text), button (String url_off, String url_on), // rollover button button (String url_off, String url_on, Int32 w, Int32 h), // idem with size immediate_selector (WebArgName name, Int32 size, List((WebArgValue,String)) choices), immediate_selector (WebArgName name, Int32 size, List((WebArgValue,String)) choices, InitialValue selected). public type Actioner_Local_Action: close_window. public define Actioner_Aspect link ( String text ) = link([],text). public define Actioner_Aspect link ( List(Text_Option) options, Int32 i ) = link(options,integer_to_string(i)). public define Actioner_Aspect link ( Int32 i ) = link([],i). public define Actioner_Aspect button ( String url_img ) = button(url_img,url_img). Actioners are explained in details below. public type TextAreaOption: disabled, read_only, wrap_lines. public type HTML_In_Form: literal_pt (Printable_tree), literal (String), sequence (List(HTML_In_Form) items), text (List(Text_Option), String the_text), preformated (List(Text_Option), String), paragraph (List(Text_Option), HTML_In_Form content), image (String url), image (String url, Int32 width, Int32 height), table (List(Table_Option), HTML_Header_Row(HTML_In_Form), List(HTML_Row(HTML_In_Form))), center (HTML_In_Form), mail_to (String email, HTML_In_Form element), scroller (Int32 width, Int32 height, Int32 content_width, Int32 content_height, HTML_In_Form content), actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect, String action_name, List((String,String)) extra_ops, List(Actioner_Local_Action)), foreign_link (List(Text_Option), String url), foreign_link (List(Text_Option), String url, String name), private_download (String abs_path, String name, String extra_ext, Maybe((String,List((String,String)))) action), text_input (List(InputAttrs), String label_text, HtmlId id, WebArgName name, InitialValue init, Int32 width), text_input_ro (List(InputAttrs), String label_text, HtmlId id, WebArgName name, InitialValue init, Int32 width), password_input (List(InputAttrs), String label_text, HtmlId id, WebArgName name, InitialValue init, Int32 width), text_area (List(TextAreaOption), String name, String init, Int32 width, Int32 height), file_upload (String label, HtmlId id, WebArgName name, Int32 width), selector (List(InputAttrs), String label_text, HtmlId id, WebArgName name, Int32 size, List(String) choices), selector (List(InputAttrs), String label_text, HtmlId id, WebArgName name, Int32 size, List(String) choices, InitialValue selected), // List((String,String)) = List((code,name)) where : // code is the web-arg value // name appears in selector selector_c (List(InputAttrs), String label_text, HtmlId id, WebArgName name, Int32 size, List((WebArgValue,String)) choices), selector_c (List(InputAttrs), String label_text, HtmlId id, WebArgName name, Int32 size, List((WebArgValue,String)) choices, InitialValue selected), radio_button (List(InputAttrs), String label_text, HtmlId id, WebArgName name, WebArgValue value, Bool checked), radio_button_r (List(InputAttrs), String label_text, HtmlId id, WebArgName name, WebArgValue value, Bool checked), check_box (List(InputAttrs), String label_text, HtmlId id, WebArgName name, WebArgValue value, Bool checked), check_box_r (List(InputAttrs), String label_text, HtmlId id, WebArgName name, WebArgValue value, Bool checked), div (List(CoreAttrs), HTML_In_Form content), div_empty (List(CoreAttrs)), hidden (String name, String value). 'HTML_In_Form' defines all the elements you may put within a form. We define a convenience function: public define HTML_In_Form literal(Printable_tree t) = literal_pt(t). public define HTML_In_Form foreign_link ( Int32 tsize, String url, String name ) = foreign_link([size(tsize)],url,name). public define HTML_In_Form actioner ( Actioner_Connection conn, Actioner_Target targ, Actioner_Aspect asp, String action_name, List((String,String)) extra_ops ) = actioner(conn,targ,asp,action_name,extra_ops,[]). public define HTML_In_Form text_area ( String name, String init, Int32 width, Int32 height ) = text_area([],name,init,width,height). public define HTML_In_Form table ( List(HTML_Row(HTML_In_Form)) rows ) = table([],empty,rows). public define HTML_In_Form table ( List(Table_Option) options, List(HTML_Row(HTML_In_Form)) rows ) = table(options,empty,rows). public define HTML_In_Form table ( HTML_Header_Row(HTML_In_Form) h_row, List(HTML_Row(HTML_In_Form)) rows ) = table([],h_row,rows). public define HTML_In_Form private_download ( String abs_path, String name, String extra_ext ) = private_download(abs_path,name,extra_ext,failure). public define HTML_In_Form private_download ( String abs_path, String name, String extra_ext, String action_name, List((String,String)) args ) = private_download(abs_path,name,extra_ext,success((action_name,args))). public define HTML_In_Form text ( String s ) = text([],s). public define HTML_In_Form radio_button (List(InputAttrs) options, String label_text, WebArgName n, WebArgValue value, Bool checked) = radio_button (options, label_text, htmlId(n.name), n, value, checked). public define HTML_In_Form radio_button_r (List(InputAttrs) options, String label_text, WebArgName n, WebArgValue value, Bool checked) = radio_button_r (options, label_text, htmlId(n.name), n, value, checked). public define HTML_In_Form check_box (List(InputAttrs) options, String label_text, WebArgName n, WebArgValue value, Bool checked) = check_box (options, label_text, htmlId(n.name), n, value, checked). public define HTML_In_Form check_box_r (List(InputAttrs) options, String label_text, WebArgName n, WebArgValue value, Bool checked) = check_box_r (options, label_text, htmlId(n.name), n, value, checked). public define HTML_In_Form file_upload (WebArgName name, Int32 width) = file_upload("", htmlId(""), name, width). public type HTML_Off_Form: literal_pt (Printable_tree), literal (String), sequence (List(HTML_Off_Form) items), text (List(Text_Option), String the_text), preformated (List(Text_Option), String), paragraph (List(Text_Option), HTML_Off_Form content), image (String url), image (String url, Int32 width, Int32 height), table (List(Table_Option), HTML_Header_Row(HTML_Off_Form), List(HTML_Row(HTML_Off_Form))), center (HTML_Off_Form), mail_to (String email, HTML_Off_Form element), scroller (Int32 width, Int32 height, Int32 content_width, Int32 content_height, HTML_Off_Form content), fixed_size (HTML_Size width, HTML_Size height, HTML_Off_Form content), fixed_size_2 (HTML_Size width, HTML_Size height, String name_of_HTML_file), actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect, String action_name, List((String,String)) extra_ops, List(Actioner_Local_Action)), actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect, String action_name, List((String,String)) extra_ops, List(Actioner_Local_Action), String form_name), foreign_link (List(Text_Option), String url), foreign_link (List(Text_Option), String url, String name), private_download (String abs_path, String name, String extra_ext, Maybe((String,List((String,String)))) action), label (String name), form (String form_name, List(CoreAttrs), HTML_In_Form content), div (List(CoreAttrs), HTML_Off_Form content), div_empty (List(CoreAttrs)). 'HTML_Off_Form' defines all the elements you may put outside any form. public define HTML_Off_Form literal(Printable_tree t) = literal_pt(t). public define HTML_Off_Form fixed_size(HTML_Size width, HTML_Size height, String name_of_HTML_file) = fixed_size_2(width,height,name_of_HTML_file). public define HTML_Off_Form foreign_link ( Int32 tsize, String url, String name ) = foreign_link([size(tsize)],url,name). public define HTML_Off_Form actioner ( Actioner_Connection conn, Actioner_Target targ, Actioner_Aspect asp, String action_name, List((String,String)) extra_ops ) = actioner(conn,targ,asp,action_name,extra_ops,[]). public define HTML_Off_Form table ( List(HTML_Row(HTML_Off_Form)) rows ) = table([],empty,rows). public define HTML_Off_Form table ( List(Table_Option) options, List(HTML_Row(HTML_Off_Form)) rows ) = table(options,empty,rows). public define HTML_Off_Form table ( HTML_Header_Row(HTML_Off_Form) h_row, List(HTML_Row(HTML_Off_Form)) rows ) = table([],h_row,rows). We add two convenience functions for 'row'. The reason why we add two functions, one for 'HTML_In_Form' and one for 'HTML_Off_Form', is that adding a schema with an arbitrary '$T' creates too many ambiguities. This is due to the fact that, if we do so, the arguments of the function do not refer to any of the types defined here. public define HTML_Row(HTML_In_Form) row ( HTML_In_Form content ) = row([],[cell([],content)]). public define HTML_Row(HTML_Off_Form) row ( HTML_Off_Form content ) = row([],[cell([],content)]). public define HTML_Off_Form private_download ( String abs_path, String name, String extra_ext ) = private_download(abs_path,name,extra_ext,failure). public define HTML_Off_Form private_download ( String abs_path, String name, String extra_ext, String action_name, List((String,String)) args ) = private_download(abs_path,name,extra_ext,success((action_name,args))). public define HTML_Off_Form text ( List(Text_Option) lto, Int32 i ) = text(lto,integer_to_string(i)). public define HTML_Off_Form text ( Int32 i ) = text([],i). public define HTML_Off_Form text ( String s ) = text([],s). - Cell a gap between two other cells : public define HTML_Cell(HTML_Off_Form) width_gap ( Int32 w ) = cell([width(w)],text([],"")). public define HTML_Cell(HTML_In_Form) width_gap ( Int32 w ) = cell([width(w)],text([],"")). - Row a gap between two other rows : public define HTML_Row(HTML_Off_Form) height_gap ( Int32 h ) = row([],[cell([height(h)],text([],""))]). public define HTML_Row(HTML_In_Form) height_gap ( Int32 h ) = row([],[cell([height(h)],text([],""))]). Notice that the two types have alternatives in common (same name, same arguments types, up to the value of the parameter $T), which correspond to elements which may be put anywhere in the page. public type CSS_Style: text_options(List(Text_Option)). public type CSS_File: css_file(String file_name). public type JS_File: js_file(String file_name). define String format ( List(Text_Option) l ). define Printable_tree format_css_styles ( List(CSS_Style) l ) = if l is { [ ] then [ ], [h . t] then [ if h is { text_options(tos) then [" body, span, p { ", format(tos), " }\n" ] } . format_css_styles(t)] }. public type HTML_Meta: keywords (List(String)), refresh (Actioner_Connection connection, Actioner_Target target, String action_name, Int32 delay), // in seconds meta (String name, String content), http_equiv (String name, String content), generic_meta (List((String,String))), literal (String). Meta tags are put in the 'head' of the HTML page. public type Body_Option: background_color (RGB), background_image (String url), background_image (String url, List(BackgroundOption)). public type HTML_Body: body(List(Body_Option) options, HTML_Off_Form content). public type HTML_Page: html_page(String title, List(HTML_Meta) meta_tags, List(CSS_Style) styles, List(CSS_File) css_files, List(JS_File) js_files, HTML_Body body). public define HTML_Page html_page ( String title, List(HTML_Meta) metas, HTML_Body body ) = html_page(title,metas,[],[],[],body). public define HTML_Page html_page ( String title, List(HTML_Meta) metas, List(CSS_Style) styles, HTML_Body body ) = html_page(title, metas, styles, [], [], body). 'HTML_Page' represents the final product of the construction of a web page. *** (3.2) ``in form'' versus ``off form''. There is a variety of HTML elements: texts, buttons, links, forms, inputs, etc... Some of them may have a content, which is yet another HTML element (or several). Hence, it is meaningful to say that an element is 'within' another one. Now, putting any element within any other one may be meaningless. For example, an input element must be put within a form (otherwise, it is useless), and a from within another form has no precise meaning (and is forbidden by the HTML specification). Actually, the main criterium is ``within a form or not within a form''. So, HTML elements in a given page are separated into two categories: those who are within a form, and the others. Nevertheless, there are elements which may belong to both categories, like images and texts. We want to make use of the strong typing mecanism of Anubis in order to forbid non meaningful placement of elements. The type 'HTML_In_Form' defines elements to be put within forms. Similarly, 'HTML_Off_Form' defines elements not to be put within forms. Both types are recursive, and 'HTML_Off_Form' refers to 'HTML_In_Form' (via the 'form' alternative, of course), but the two types are not cross recursive. This is the reason why it is impossible to put a form within a form. In order to construct a web page, you essentially have to produce a datum of type 'HTML_Off_Form' (maybe containing data of type 'HTML_In_Form'). In practice, you don't have to worry so much about these two types, because elements which may be put anywhere are constructed for both types by functions with the same name and the same arguments. Hence, for both types, you just write the same thing. You are warned by the compiler only when you try to put an element at a place it is not allowed. *** (3.3) Defining your own style. We provide generic tools for constructing HTML elements. However, your web site needs to have a ``style''. To that end, you need to write down a set of ``styling functions'', using the tools defined here. These styling functions allow the introduction of your colors and other visual characteristics into the constructed elements once and for all. For example, you may want all your texts to be rendered in the ``Helvetica'' font, in size 14 and using some 'text_color'. You may write something like this: define RGB text_color = rgb(10,40,40). define HTML_Off_Form text ( String the_text ) = text([font("helvetica"),size(14),color(text_color)], the_text). (and the same one for type 'HTML_In_Form') so that in order to put a piece of text in a page, you just write: text("... some text ...") and you don't have to provide the font, size and color for each text. If you want to have several styles of text presentation, you just write several sets of such convenience functions. This also suggests a trick. You may want for example different colors for 'in form' texts and 'off form' texts. This may be achieved automatically by defining two functions as above, with the same name and same argument type, but returning either a 'HTML_In_Form' or a 'HTML_Off_Form'. If this preliminary work is well done, you will not waste your time later when you concentrate on the actual informational content of your pages. This general principle should be applied to all sorts of elements. This is the best thing to do in order to separate the functions defining the visual style from the functions defining the informational content itself, so that changing the style without changing the content becomes easy. This is also the best way for having a clean and easily readable source for your web site. *** (3.4) Actioners and forms. We have gathered several notions from HTML into that of an 'actioner'. An actioner is an HTML element which opens a connection to our server when clicked upon. Actioners may have different visual aspects. They may look like hypertext links or like buttons (rollovers), or even like selectors (with immediate action). In any case, their behavior is the same: they open a connection to our server, and send a set of 'web arguments', i.e. pairs 'name=value'. Among these web arguments, one of them denotes the action to be performed, and the others should be considered as operands for this action. Actually, the precise behavior of the actioner has several variants. The connection with the server may be secured (HTTPS) or non secured (HTTP). See the type 'Actioner_Connection' above. You must also choose where the answer must be rendered. This may be in the same window or in another window (or frame). If it is in another window, the name of that window must be given. If the window does not exist, the browser will create it. Optionally, you may give the dimensions of the new window and other characteristics. See the type 'Actioner_Target' above. The actioner also has a visual aspect. See the type 'Actioner_Aspect' above. In the case of a rollover button, you provide the URLs of two images (of the same size) representing the button: url_off: to be used when the mouse is not over the button, url_on: to be used when the mouse is over the button. You can also create rollover buttons without creating images. Just use the second alternative named 'button'. The server creates the images automatically. The purpose of forms is just to give operands to actioners. If the actioner is placed within a form, all the input elements which are within this form provide operands to the actioner (except sometimes when they are not set by the client). If it is not put within a form, the actioner gets no operand, except if the name of a form is explicitly given, in which case the actioner gets all the inputs from that form as operands. Furthermore, you may want to give extra operands to the actioner. This may be useful for separating families of actioners with the same action name. Extra operands 'name=value' must be given in the form of pairs '(name,value)'. Notice that the name of a form may be used by an actioner which is off the form, so as to get the operands provided by this form. Also notice that several actioners may refer to the same form, being either in the form, or referring to the form from the outside. These actioners simply get the same set of operands, even if they correspond to distinct actions. Input elements may be put only within a form. *** (3.5) Local popup. This element looks like a link or a rollover button. When this button is clicked upon, a 'popup window' appears. Actually, this popup window is just a layer in the same HTML page, which becomes suddenly visible. It is realized with a '
' HTML tag. In particular, clicking on the button does not open any connection. This is why it is called 'local'. The arguments have the following roles: Actioner_Aspect aspect of the button (same semantics as for actioners) content content of the popup window x, y, position of the popup window on the HTML page (not relative to the button but to the page itself) title title of the popup window color color of the title bar and close button in the popup window. A lightened version of this color is used for the background of the popup window. width width of the title bar --- That's all for the public part ! -------------------------------------------------- ----------------------------------- Table of Contents --------------------------------- *** [1] States. *** [1.1] Saving and retrieving states. *** [1.2] Deleting out of date states. *** [2] Tools. *** [2.1] Directories. *** [2.2] Secondary documents. *** [3] Managing web arguments. *** [3.1] Prefixing web arguments names. *** [3.2] Separating web arguments. *** [3.3] Applying an action. *** [4] Web site descriptions and the 'awp handlers'. *** [4.1] The type 'Web_Site'. *** [4.2] Making a web site description. *** [4.3] Starting the servers. *** [5] HTML Formating. *** [5.1] The type 'HTML_Any($T)'. *** [5.2] Formating a color. *** [5.3] Creating buttons. *** [5.4] Formating an actioner. *** [5.5] Formating a private download link. *** [5.6] Formating rows and cells in a table. *** [5.7] Formating elements which may be put anywhere. *** [5.8] Formating 'in form' elements. *** [5.9] Formating 'off form' elements. *** [5.10] Formating meta-tags. --------------------------------------------------------------------------------------- public define String doctype_w3c_header = "\n". *** [1] States. We have to define functions for saving a state, retrieving a state, deleting out of date states. We need one such function per web site. The types of the first two functions depend on the parameter $State. This is not the case of the third one. The fact that the instance of $State is variable from one web sites to the other implies rather subtle manipulations using full functionality. *** [1.1] Saving and retrieving states. Each state is saved into a file on the server's disk (in the directory represented by the symbol 'state_directory', which is 'my_anubis/web_sites/common_name/states'). The state is saved together with a time stamp whose value is obtained by adding the current time to the given timeout for states. The state receives a name obtained by hashing (using sha1) the content of the file itself, and then encoding the hash with 'web_arg_encode'. The name of the file into which the state is saved is the concatenation of "s" and the name of the state. read CXM_web_arg_encode.anubis The tool below constructs the function which is able to save a state on the server's disk. define (Maybe($State) s) -> String // the function constructed returns the name of the state make_save_state_function ( Int32 timeout, String state_directory ) = (Maybe($State) mbs) |-> if mbs is { failure then "", success(s) then with time_stamp = now+timeout, to_be_saved = (time_stamp,s), state_name = web_arg_encode(sha1(s)), if save(to_be_saved,state_directory+"/s"+state_name) is ok then state_name else (print("Cannot create state file in '"+state_directory+"'.\n"); "") }. When a request arrives, we need to retrieve the previous state from the server's disk. We receive the name of that state. If the state is out of date, the state file is kept 3 days, and then deleted. type PreviousState($State): not_found, // cannot retrieve the previous state out_of_date($State), // the previous state is out of date still_valid($State). // the previous state is still valid define (String state_name) -> PreviousState($State) make_retrieve_state_function ( String state_directory ) = (String state_name) |-> with file_path = state_directory+"/s"+state_name, if (RetrieveResult((Int32,$State)))retrieve(file_path) is ok(d) then ( if d is (time_stamp,s) then if time_stamp < now then ( forget(remove(file_path)); out_of_date(s) ) else still_valid(s) // state has been successfully retrieved ) else not_found. *** [1.2] Deleting out of date states. We also need to delete states which are out of date and which will never be deleted by the above method. This may be performed by a machine doing this periodically (say once per states life time period). define (List(String) file_names) -> One make_delete_out_of_date_states_function ( Maybe($State) dummy, String state_directory ) = (List(String) file_names) |-df-> if file_names is { [ ] then unique, [h . t] then with file_path = state_directory+"/"+h, if (RetrieveResult((Int32,$State)))retrieve(file_path) is ok(d) then ( if d is (time_stamp,data) then if time_stamp < now then (forget(remove(file_path)); df(t)) else df(t) ) else (forget(remove(file_path)); df(t)) }. The 'labelled arrow' |-df-> is documented in 'documentation/en/anubis_doc.txt'. Note: The argument 'dummy' (of type Maybe($State)) is not used in the body of the function (hence its name). Nevertheless, it is required. Indeed, the Anubis compiler does not accept a parameter in the body of a function (here the parameter is required by the use of 'retrieve') if this parameter does not appear in the type of the function. This is because this would create ambiguities that no explicit typing may ever resolve. If you put a double slash in front of the declaration of 'dummy' above, and if you compile this file, you will get a message like this one: Error in 'making_a_web_site.anubis', line 1300, column 7: A definition may not contain parameters which are not present in the declaration part (hidden parameters): $State The type of the function constructed by 'make_delete_out_of_date_states_function' is independant of the parameter $State. This is important because this allows to create the list of such functions for all web sites. From this list, it is possible to call the functions one after the other, so deleting out of date states for all web sites. Actually, the next function receives a list of pairs (state_directory,function), one for each web site. define One delete_out_of_date_states // for all web sites ( List((String, List(String) -> One)) directories_and_functions ) = if directories_and_functions is { [ ] then unique, [h . t] then if h is (state_directory,function) then function(directory_list(state_directory,"s*")); delete_out_of_date_states(t) }. The above function must be called periodically in a separate virtual machine. The period we have choosen is (rather logically) the life time of states itself. This may be achieved by an 'infinite' loop, using a 'sleep(timeout)'. However, the loop must not be really infinite, because the servers may be shutdown. Hence, our loop must test (rather frequently; say every second) if the servers are down. If they are, the loop must be exited. define One delete_states_loop ( List((String,List(String) -> One)) directories_and_functions, Int32 timeout, Int32 next_time, Server http_server, Server https_server, Var(Bool) shutdown_required ) = if *shutdown_required then (shutdown(http_server); shutdown(https_server)) else unique; if (is_down(http_server) & is_down(https_server)) then unique else if now > next_time then ( delete_out_of_date_states(directories_and_functions); delete_states_loop(directories_and_functions, timeout, now+timeout, http_server, https_server, shutdown_required) ) else ( sleep(1000); // sleep just one second and try again delete_states_loop(directories_and_functions, timeout, next_time, http_server, https_server, shutdown_required) ). The above loop must be run in a separate virtual machine. This will be done just after the two servers are started. *** [2] Tools. *** [2.1] Directories. We need a tool for creating directories (if needed). (This tool has been moved to 'tools/basis.anubis'). *** [2.2] Secondary documents. Some HTML elements (like '', '') cannot receive their content directly from the current document, but only through an URL. For this reason, we implement a mecanism for creating secondary documents on the fly. To that end we use the 'private download' mecanism. A secondary document is formated by the same functions as the main document itself. The next function takes an 'off form' element, creates the file containing the secondary document in HTML format, and returns the URL at which the document will be available. define String create_secondary_document ( String sd, // site directory String as, // authorization_secret String sn, // state name $T -> Printable_tree format_element, $T content, HTML_Size width ) = with private_download_directory = sd+"/private_download", hash = web_arg_encode(sha1(content)), file_content = (Printable_tree) [doctype_w3c_header, "
", format_element(content), "
" ], file_name = "sd"+hash+".html", file_path = private_download_directory+"/"+file_name, if write_to_file(file_path,file_content) is { cannot_open_file then print("Cannot open file '"+file_path+"'.\n"); "", write_error(n) then print("Error writing file '"+file_path+"'.\n"); "", ok then file_name+"?zauth="+ make_authorization(sd,as,private_download_directory+"/"+file_name) }. *** [2.3] Generating unique ids. In order to uniquely name object for JavaScript we generate unique ids from a counter. define Int32 new_idnum ( Var(Int32) ic_v // 'idnum' counter variable ) = protect with result = *ic_v+1, ic_v <- result; result. *** [3] Managing web arguments. Web arguments are those pairs 'name=value' which are transmitted through the HTTP protocol. We need precise naming conventions for these web arguments. *** [3.1] Prefixing web arguments names. We want to assign different roles to web arguments, and we also want to be able to recognize its role directly from the name of a web argument. The name "s" is reserved for the web argument whose value is the name of the current state. The name "a" is reserved for the web argument whose value is the name of the action to be performed. Other web arguments receive arbitrary names, and in order to avoid clashes, these names are prefixed by: "p" for names of password inputs, "o" for other web arguments The reason why password input names have a distinct prefix is that this allows the HTTP server to hide the passwords on the console of the server and in the journal. *** [3.2] Separating web arguments. When a new request arrives, we need to separate the web arguments, that is to say: - find the value of "s", and recover the corresponding state, - find the value of "a", which is the name of the action to be performed, - get the list of all the remaining web arguments (operands of the action). We must also determine if the previous state may be recovered. If it is not the case (either because the previous state name is invalid, or the previous state is out of date), we must check if there is an action name. Indeed, the presence of an action name indicates that the user has clicked on one of our buttons or links. If on the contrary there is no action name the user has just entered our address in his browser. In this last case, we must send the first page of our site (maybe a 'login' page), but if there is an action, we must send a page just saying that the session ticket has expired. If the previous state is recovered and there is no action, the new state is the same as the previous state. The result of the separation of the web arguments is of type: type Separated_Web_Args($State): swa(Maybe(PreviousState($State)) previous_state, Maybe(String) action_name, List(Web_arg) operands). The next function constructs the function which separates the web arguments. define (List(Web_arg) lwa) -> Separated_Web_Args($State) make_separate_web_args_function ( String state_directory, String -> PreviousState($State) retrieve_state ) = (List(Web_arg) lwa) |-swaf-> if lwa is { [ ] then // // no web arg found => no previous state and no action // swa(failure,failure,[]), [wa_1 . wa_others] then // // at least one web arg => // separate other web args, and insert the first one as needed // if (Separated_Web_Args($State))swaf(wa_others) is { swa(ps1, // possible previous state an1, // maybe an action name op1) // operands so far then if wa_1 is { web_arg(n,v) then with prefix = if substr(n,0,4) = "amp;" then substr(n,4,1) else substr(n,0,1), name_start = (Int32)(if substr(n,0,4) = "amp;" then 5 else 1), if prefix = "s" then swa(success(retrieve_state(v)),an1,op1) else if prefix = "a" then swa(ps1,success(v),op1) else if prefix = "t" then swa(ps1,an1,[web_arg("target",v) . op1]) else if prefix = "p" then swa(ps1,an1,[web_arg(substr(n,name_start,length(n)-name_start),v) . op1]) else if prefix = "o" then swa(ps1,an1,[web_arg(substr(n,name_start,length(n)-name_start),v) . op1]) else swa(ps1,an1,op1), upload(n,v,t) then swa(ps1,an1,[upload(substr(n,1,length(n)-1),v,t) . op1]) }} }. *** [3.3] Applying an action. When the web arguments are separated (and their names cleaned up from prefixes), we may apply the action to the operands and the current state. We search for the action to be applied in the list of actions. If no action is found, the new state is the same as the previous state. Also, we deny the application of an HTTP action if the request arrives through the HTTPS channel and conversely. define (Maybe($State) previous, String action_name, HTTP_Info http_info, List(Web_arg) lwa, Bool is_https) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) make_apply_action_function ( List(Web_Action($SessionTicket, $State)) actions ) = with f = (Maybe($State) previous, String action_name, HTTP_Info http_info, List(Web_arg) lwa, Bool is_https, List(Web_Action($SessionTicket, $State)) actions) |-f-> if actions is { [ ] then (print("action '"+action_name+ "' not found.\n"); (failure, previous, [])), [ac1 . others] then if ac1 is { http_action(an,allow,do_it) then if an = action_name then if is_https then (print("HTTP action '"+an+ "' called through HTTPS (denied).\n"); (failure, previous, [])) else if allow(previous) then do_it(http_info,lwa,previous) else (failure, previous, []) else f(previous,action_name,http_info,lwa,is_https,others), https_action(an,allow,do_it) then if an = action_name then if is_https then if allow(previous) then do_it(http_info,lwa,previous) else (failure, previous, []) else (print("HTTPS action '"+an+ "' called through HTTP (denied).\n"); (failure, previous, [])) else f(previous,action_name,http_info,lwa,is_https,others), http_https_action(an,allow,do_it) then if an = action_name then if allow(previous) then do_it(http_info,lwa,previous) else (failure, previous, []) else f(previous,action_name,http_info,lwa,is_https,others), } }, (Maybe($State) previous, String action_name, HTTP_Info http_info, List(Web_arg) lwa, Bool is_https) |-> f(previous,action_name,http_info,lwa,is_https,actions). *** [4] Web site descriptions and the 'awp handlers'. *** [4.1] The type 'Web_Site'. The type 'Web_Site_Description' is defined in 'web/multihost_http_server.anubis'. We need another one, because, we have some extra informations to record for each site. public type Web_Site: web_site((Int32,Int32) -> Web_Site_Description description, List(String) -> One delete_out_of_date). *** [4.2] Making a web site description. Below is the function which creates a web site description. It first creates (if needed) the directories for the site, then constructs the tool functions for the site, and the site handler. Finally, it constructs the web site description. We gather common (constant) informations in the following type: type CommonInfo: info(String common_name, Int32 http_port, Int32 https_port, String site_directory, String authorization_secret ). We need a forward declaration. public define Printable_tree format ( CommonInfo cinfo, String state_name, HTML_Page page, Bool is_https, String charset ). define Printable_tree format ( HTML_Size s ) = if s is { absolute(x) then ["\"",x,"\""], percentage(x) then ["\"",x,"%\""] }. define Printable_tree top_redirection_page ( String common_name ) = [ doctype_w3c_header, "", "", "" ]. public define Web_Site make_web_site_description ( List(String) common_names, // for example: ["www.our-business.com"] String site_directory, One -> One init, (HTTP_Info) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) initial_state, ($State expired, HTTP_Info, List(Web_arg), Bool is_https) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) ticket_expired_state, (HTTP_Info, List(Web_arg), Bool is_https) -> (Maybe($SessionTicket), Maybe($State), List(HTTP_header)) ticket_lost_state, List(Web_Action($SessionTicket, $State)) actions, (Maybe($SessionTicket), Maybe($State)) -> HTML_Page compute_page, Int32 timeout, List(Redirection) redirections, String charset, List(String) journal_extensions, List(String) journal_headers, String secret, List(MIME) known_mime_types, (String action_name, List(Web_arg) args) -> One before_send_file ) = init(unique); // // make required directories (if needed) // with web_sites_directory = make_directory(my_anubis_directory+"/web_sites"), base_directory = make_directory(site_directory), state_directory = make_directory(site_directory+"/states"), forget(make_directory(site_directory+"/public")); // // construct tool functions // with save_state = make_save_state_function(timeout,state_directory), retrieve_state = make_retrieve_state_function(state_directory), separate_web_args = make_separate_web_args_function(state_directory,retrieve_state), apply_action = make_apply_action_function(actions), // // construct the site handler // site_handler = (Int32 http_port, Int32 https_port) |-> ((String host_name, HTTP_Info http_info, List(Web_arg) lwa, Bool is_https) |-> ((List(HTTP_header),Printable_tree)) if separate_web_args(lwa) is { swa(mb_previous_state,mb_action_name,operands) then with state_and_headers = if mb_previous_state is { failure then if mb_action_name is { failure then initial_state(http_info), success(action_name) then apply_action(failure,action_name,http_info,operands,is_https) }, success(previous_state) then if previous_state is { not_found then if mb_action_name is { failure then initial_state(http_info), success(_) then ticket_lost_state(http_info,lwa,is_https) }, out_of_date(state) then ticket_expired_state(state,http_info,lwa,is_https), still_valid(state) then if mb_action_name is { failure then (failure, success(state), []), success(action_name) then apply_action(success(state),action_name,http_info,operands,is_https) } } }, if state_and_headers is (session_ticket, mb_new_state, headers) then with state_name = save_state(mb_new_state), (headers, format(info(host_name, http_port, https_port, site_directory, secret), state_name, compute_page(session_ticket, mb_new_state), is_https, charset)) }), // // make the delete_out_of_date function // delete_out_of_date = make_delete_out_of_date_states_function((Maybe($State))failure, site_directory+"/states"), // // construct the web site description // web_site((Int32 http_port, Int32 https_port) |-> web_site_description(common_names, site_directory, redirections, charset, journal_extensions, journal_headers, secret, known_mime_types, site_handler(http_port,https_port), (List(Web_arg) lwa) |-> if separate_web_args(lwa) is swa(mb_previous_state,mb_action_name,operands) then if mb_action_name is { failure then unique success(an) then before_send_file(an,operands) }), delete_out_of_date). *** [4.3] Starting the servers. public define Start_Web_Sites_Result start_web_sites ( Int32 ip_address, // the IP address shared by the web sites Int32 http_port, // usually: 80 Int32 https_port, // usually: 443 String ssl_certificate_common_name, List(Web_Site) web_sites, // web sites to be started Var(Bool) shutdown_required ) = with get_description = (Web_Site ws) |-> description(ws)(http_port,https_port), with http_server_r = start_http_server(ip_address,http_port, map(get_description,web_sites), load_denial_of_service_info), with https_server_r = start_https_server(ip_address,https_port, ssl_certificate_common_name, map(get_description,web_sites), load_denial_of_service_info), if http_server_r is ok(http_server) then ( if https_server_r is ok(https_server) then ( start_http_servers_tasks(map(get_description,web_sites), [http_server,https_server], 600); // period of 10 minutes delegate delete_states_loop( map((Web_Site ws) |-> (site_directory(description(ws)(http_port,https_port))+ "/states",delete_out_of_date(ws)), web_sites), 3600*24*3, // keep out of date states 3 days now, http_server, https_server, shutdown_required), ok(http_server,https_server) ) else cannot_bind_to_port(https_port) ) else ( if https_server_r is ok(https_server) then cannot_bind_to_port(http_port) else cannot_bind_to_port(http_port,https_port) ). public define One start_web_sites ( Int32 ip_address, // the IP address shared by the web sites Int32 http_port, // usually: 80 Int32 https_port, // usually: 443 String ssl_certificate_common_name, List(Web_Site) web_sites, // web sites to be started Var(Bool) shutdown_required, Logger log ) = if (Start_Web_Sites_Result)start_web_sites(ip_address, http_port, https_port, ssl_certificate_common_name, web_sites, shutdown_required) is { cannot_bind_to_port(n) then logError(log, "Cannot bind to port: "+n), cannot_bind_to_port(n,m) then logError(log, "Cannot bind to ports: "+n+", "+m), ok(s1,s2) then logInfo(log, "Servers started.") }. *** [5] HTML Formating. We need to translate HTML elements as defined above into actual HTML text. Actioners require special informations, which must be transmitted when needed by the 'format' functions: - the 'common name', which is used for URLs, - the HTTP/HTTPS port number, - the 'state name', which must be transmitted when the actioner is clicked upon, - the 'form name' (if any) to which the actioner refers. If the actioner is off form, and if it refers to a form, the name of that form is already known by the actioner. On the contrary, if the actioner is 'in form', it refers implicitly to the form containing it. The name of that form is transmitted to the 'format' functions called from within the formating of that form. *** [5.1] The type 'HTML_Any($T)'. The type 'HTML_Any($T)' gathers elements which may be put anywhere in the page. The parameter $T becomes either 'HTML_Off_Form' or 'HTML_In_Form'. type HTML_Any($T): any_text (List(Text_Option), String the_text), any_preformated (List(Text_Option), String), any_paragraph (List(Text_Option), $T content), any_image (String url), any_image (String url, Int32 width, Int32 height), any_table (List(Table_Option), HTML_Header_Row($T), List(HTML_Row($T))), any_center ($T), any_mail_to (String email, $T element), any_scroller (Int32 width, Int32 height, Int32 content_width, Int32 content_height, $T content), any_fixed_size (HTML_Size width, HTML_Size height, $T content), any_fixed_size_2 (HTML_Size width, HTML_Size height, String name_of_HTML_file), any_actioner (Actioner_Connection, Actioner_Target, Actioner_Aspect, String action_name, List((String,String)) extra_ops, List(Actioner_Local_Action), Maybe(String) form_name), any_foreign_link (List(Text_Option), String url), any_foreign_link (List(Text_Option), String url, String name), any_private_download (String abs_path, String name, String extra_ext, Maybe((String,List((String,String))))), any_div (List(CoreAttrs), $T element), any_div_empty (List(CoreAttrs)), any_coreattrs (List(CoreAttrs)). *** [5.2] Formating a color. RGB colors are formatted as '#rrggbb' where rr, gg and bb are two characters hexadecimal values. define String html_format ( RGB color ) = if color is rgb(r,g,b) then "#" + hexadecimal(word8_to_int32(r),2) + hexadecimal(word8_to_int32(g),2) + hexadecimal(word8_to_int32(b),2). The following is a very arbitrary definition of the opposite color. The thing which is important is that it is far from the original, so that characters in 'opposite' color are clearly visible over the original. define RGB opposite ( RGB color ) = if color is rgb(r,g,b) then with r1 = word8_to_int32(r), with g1 = word8_to_int32(g), with b1 = word8_to_int32(b), rgb(truncate_to_word8(255-r1), truncate_to_word8(255-g1), truncate_to_word8(255-b1)). *** [5.3] Creating buttons. We want to be able to create buttons in the form of a pair of images (rollovers) automatically. We use the JPEG interface, because for the time being Anubis cannot handle other kinds of images. Computing printed text length. define Int32 printed_text_width ( Word8 -> Int32 char_size, List(Word8) l ) = if l is { [] then (Int32) 0, [h . t] then char_size(h) + 1+ printed_text_width(char_size,t) }. define Int32 printed_text_width ( SystemFont font, String s ) = printed_text_width((Word8 c) |-> word8_to_int32(width(get_char_info(font,c))), explode(s)). Converting RGB to RGBA. define RGBA to_rgba ( RGB color ) = if color is rgb(r,g,b) then rgba(r,g,b,255). Drawing a 'relief'. define One draw_relief ( RGBAImage dest, RGBA color, Int32 contrast, Int32 x, Int32 y, Int32 width, Int32 height ) = with l = lighten(color,contrast), d = darken(color,contrast), draw_rectangle(dest,rect(x,y,x+width,y+1),l); draw_rectangle(dest,rect(x,y+1,x+1,y+height),l); draw_rectangle(dest,rect(x+width-1,y+1,x+width,y+height),d); draw_rectangle(dest,rect(x+1,y+height-1,x+width-1,y+height),d). Creating a button background. define RGBAImage create_button_background ( RGBA color, Int32 width, Int32 height ) = with result = create_rgba_image(width,height,color), draw_relief(result,color,100,0,0,width,height); draw_relief(result,color,70,1,1,width-2,height-2); draw_relief(result,color,55,2,2,width-4,height-4); draw_relief(result,color,35,3,3,width-6,height-6); draw_relief(result,color,20,4,4,width-8,height-8); draw_relief(result,color,10,5,5,width-10,height-10); draw_relief(result,color,5,6,6,width-12,height-12); result. Drawing the text over the background. define One draw_button_text ( RGBAImage image, String text, Int32 text_index, Int32 pixel_x, Int32 y, Rectangle clip, RGBA color, SystemFont font, ) = if nth(text_index,text) is { failure then unique, success(c) then with cw = draw_system_character(image,clip,pixel_x,y,font,word8_to_int32(c),color), draw_button_text(image,text,text_index+1,pixel_x+cw+1,y,clip,color,font) }. define One draw_button_text ( RGBAImage image, String text, Int32 text_width, RGBA light_color, RGBA dark_color, SystemFont font ) = with image_width = width(image), image_height = height(image), x_pos = (image_width-text_width)>>1, clip = rect(0,0,image_width,image_height), new_light_color = lighten(light_color,150), new_dark_color = darken(dark_color,40), draw_button_text(image, text, 0, x_pos+2, 16, clip, new_dark_color, font); draw_button_text(image, text, 0, x_pos, 14, clip, new_light_color, font). The next function creates the two images for a button. The information given is the main color of the button, the text of the button and the minimal width (in pixels) of the button. The function does not create the button if the images already exist. The two images are stored in the directory 'site_directory/buttons'. The names of the files are of the form: bxxxx_off.jpg bxxxx_on.jpg where the prefix 'b' is to avoid leading '-' which may perturb UNIX commands (like 'rm'), and where 'xxxx' is created from the given informations by the formula: xxxx = web_arg_encode(sha1((color,text,width))) Hence, distinct informations give distinct file names. define String // returns xxxx create_button_images ( String site_directory, RGBA color, String text, Int32 width, SystemFont font ) = with xxxx = web_arg_encode(sha1((color,text,width))), buttons_dir = site_directory+"/public/buttons", off_filepath = buttons_dir+"/b"+xxxx+"_off.jpg", on_filepath = buttons_dir+"/b"+xxxx+"_on.jpg", if file_exists(on_filepath) then xxxx else with text_width = printed_text_width(font,text), button_width = max(width,text_width+12), button_height = (Int32)20, light_color = lighten(color,60), very_light_color = lighten(light_color,30), dark_color = darken(color,40), background_off = create_button_background(color,button_width,button_height), background_on = create_button_background(light_color,button_width,button_height), draw_button_text(background_off,text,text_width,very_light_color,dark_color,font); draw_button_text(background_on, text,text_width,very_light_color,dark_color,font); forget(write_image_to_JPEG_file(to_JPEG(background_off), off_filepath, 100)); forget(write_image_to_JPEG_file(to_JPEG(background_on), on_filepath, 100)); xxxx. *** [5.4] Formating an actioner. An actioner works as follows. Assume first that it refers to a form. When it is clicked upon, the actioner puts (via 'onMouseDown') the URL into the 'action' attribute of the form, and submits the form, using the JavaScript command 'form_name.submit()'. If the actioner does not refer to a form, it fires the URL directly via 'href', because in that case, the actioner is always an tag. The URL itself is composed using the connection sort (same, http or https), the common name and port number (if needed), the state name, the action name, and the extra operands, which are put into a query string. It may look like this: http://common_name:port/?s=state_name&a=action_name&oname=value... Each extra operand is a pair of strings: (name,value). It is formated as: &oname=value define String format_extra_operands ( List((String,String)) l ) = if l is { [ ] then "", [h . t] then if h is (n,v) then "&o"+n+"="+v+format_extra_operands(t) }. It seams that the standard requires "&" instead of "&" ! In case the target is another window, we need to format the options for this window. define String format ( List(Other_Window_Option) l ) = if l is { [ ] then "", [h . t] then if h is { resizable then "resizable", scrollbars then "scrollbars", width(w) then "width="+w, height(h) then "height="+h } + if t is [ ] then "" else (","+format(t)) }. Formating choices for a "] } ], submit(options, text) then [ if action is { url(u) then ["", text, "" ], javascript(s,h) then [s,""] } ], button(url_off,url_on) then [ if action is { url(u) then ["", "\"",url_off,"\"", "" ], button(url_off,url_on,w,h) then [ if action is { url(u) then ["", "\"",url_off,"\"", "" ], immediate_selector(name,size,choices) then [ if action is { url(u) then ["" ], immediate_selector(name,size,choices,selected) then [ if action is { url(u) then ["" ], }. define Printable_tree format_local_popup_button ( CommonInfo cinfo, Actioner_Aspect aspect, Int32 n, ) = if cinfo is info(common_name,http_port,https_port,site_dir,secret) then [ "", "", if aspect is { link(opt,text) then [text], push_button(opt, text) then [text], submit(opt, text) then [text], button(url_off,url_on) then [ "\"",url_off,"\"", ], button(url_off,url_on,w,h) then [ "\"",url_off,"\"", ], immediate_selector(name,size,choices) then alert, immediate_selector(name,size,choices,selected) then alert, }, ""]. *** [5.5] Formating a private download link. We get the absolute path of the file to be downloaded, and the name under which it should appear to the client. The function 'format_private_download' creates an hypertext link for downloading the file. The secured mecanism of private download is used. This function is called by the function which formats HTML_Any($T) elements. define Printable_tree format_private_download ( CommonInfo cinfo, String sn, // state name String abs_path, // absolute file path on server String name, // name of file as it appears in the browser String extra, // extra extension Maybe((String,List((String,String)))) action ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then with private_download_directory = site_directory+"/private_download", with auth = make_authorization(site_directory,secret,abs_path), [ "", name, "" ]. *** [5.6] Formating rows and cells in a table. define Int32 percent ( Int32 p ) = if p < 0 then 0 else if p > 100 then 100 else p. Formating cell options. define String format ( BackgroundOption o ) = if o is { repeat then "", repeat_horizontal then "; background-repeat: repeat-x", repeat_vertical then "; background-repeat: repeat-y", no_repeat then "; background-repeat: no-repeat", center then "; background-position: center top" }. define String format ( List(BackgroundOption) l ) = if l is { [ ] then "", [h . t] then format(h)+format(t) }. define String format ( List(Cell_Option) options ) = if options is { [ ] then "", [h . t] then if h is { core_attrs(core_attr_list) then format_attrs(core_attr_list), left then " align=left", h_center then " align=center", right then " align=right", top then " valign=top", v_center then " valign=middle", bottom then " valign=bottom", base_line then " valign=baseline", background_color(c) then " bgcolor=\""+html_format(c)+"\"", background_image(n,o) then " style=\"background: url("+n+")"+format(o)+"\"", width(w) then " width=\""+w+"\"", percentage_width(n) then " width=\""+percent(n)+"%\"", height(h) then " height="+h, columns(n) then " colspan="+n, rows(n) then " rowspan="+n, nowrap then " nowrap" } + format(t) }. Normalizing a list of cell options (horizontal position must be specified; the default is 'left'). define List(Cell_Option) normalize ( List(Cell_Option) l ) = if member(l,left) then l else if member(l,h_center) then l else if member(l,right) then l else //[left . l]. CR: why ? there is no default because we can use CSS l. Formating cells in a row. define Printable_tree format ( List(HTML_Cell($T)) cells, $T -> Printable_tree format_element ) = if cells is { [ ] then [ ], [h . t] then if h is cell(options,element) then ["", format_element(element), "" . format(t,format_element)] }. define Printable_tree format ( List(HTML_Header_Cell($T)) cells, $T -> Printable_tree format_element ) = if cells is { [ ] then [ ], [h . t] then if h is header_cell(options,element) then ["", format_element(element), "" . format(t,format_element)] }. Formating the rows in a table. define Printable_tree format ( List(HTML_Row($T)) rows, $T -> Printable_tree format_element, ) = if rows is { [ ] then [ ], [h . t] then if h is row(options,cells) then ["", format(cells,format_element), "" . format(t,format_element)] }. define Printable_tree format ( HTML_Header_Row($T) row, $T -> Printable_tree format_element ) = if row is { empty then [ ], header_row(options,cells) then [" ", format(cells,format_element), " " ] }. define Printable_tree format1 ( List(TextAreaOption) l ) = if l is { [] then [], [h . t] then if h is { disabled then [" disabled " . format1(t)] read_only then [" readonly " . format1(t)] wrap_lines then [" wrap " . format1(t)] } }. define Printable_tree format ( List(TextAreaOption) l ) = if member(l,wrap_lines) then format1(l) else [" wrap=off " . format1(l)]. *** [5.7] Formating elements which may be put anywhere. The function below involves the parameter $T which is later instantiated as 'HTML_In_Form' or as 'HTML_Off_Form'. Now, since there are dictinct 'format' functions for these two types, and because formating of tables requires recursive calls of such functions, it is necessary to provide the 'format' function to be called recursively as an argument. Putting naively a call to 'format' will not work, because the compiler will look for a function able to format data of type $T (which is at that time distinct from any other type, including our two types). Such a function does not exist. Hence the function to be called for formating elements must be passed as a functional argument (called 'format_element' below). Actually, what we pass is a function taking a unique argument of type $T. Other informations (like the name of the state) are already in the function by way of full functionality. Formating text options. They are formated in CSS syntax, to be used within a 'style=...'. define String format ( List(Text_Option) l ) = if l is { [ ] then "", [h . t] then if h is { size(n) then "font-size:"+n+"pt", font(fn) then "font-family:"+fn, color(c) then if c is rgb(r,g,b) then "color:rgb("+word8_to_int32(r)+","+word8_to_int32(g)+","+word8_to_int32(b)+")", italic then "font-style:italic", oblique then "font-style:oblique", small_capitals then "font-variant:small-caps", bold then "font-weight:bold", underlined then "text-decoration:underline", left_justified then "text-align:left", right_justified then "text-align:right", justified then "text-align:justify", line_through then "text-decoration:line-through", nowrap then "white-space:nowrap", class(class_name)then " class=\"" +class_name +"\"", id(id_name) then " id=\"" +id_name +"\"" } + if t is [ ] then "" else ("; "+format(t)) }. Formating table options. define String format ( List(Table_Option) l, Bool border_seen ) = if l is { [ ] then if border_seen then "" else " border=0 cellspacing=0 cellpadding=0", [h . t] then if h is { background_color(c) then " bgcolor=\""+html_format(c)+"\""+format(t,border_seen), background_image(url) then " background="+url+format(t,border_seen), border(o,top,i,c) then " border="+o+" cellspacing="+top+" cellpadding="+i+ //" bordercolor="+format(c)+ format(t,true), width(w) then " width=\""+w+"\""+format(t,border_seen), percentage_width(p) then " width=\""+percent(p)+"%\""+format(t,border_seen), } }. define Printable_tree format_scroller ( String sn, Int32 width, Int32 height, Int32 content_width, Int32 content_height, Int32 idnum, // identifying the scroller $T content, $T -> Printable_tree format_element ) = [ "", "", "", "", "", "", (if content_width > width then [ "", "", "", ] else [ ]), "
", "
", "
", format_element(content), "
", "
", "
", "", "", "", "
\"sroll
\"scroll
", "
", "", "", "", "", "", "
\"scroll\"scroll
", "
" ]. /* define Printable_tree popup_topbar ( CommonInfo cinfo, String title, RGB color, Int32 width, ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then with xxxx = web_arg_encode(sha1((title,color,width))), path = site_directory+"/public/buttons/t"+xxxx+".jpg", result = (Printable_tree)["\"button\""], if file_exists(path) then result else with col = to_rgba(color), bg = create_button_background(col,width,20), very_light_color = lighten(col,70), dark_color = darken(col,40), title_width = printed_text_width(font,title), draw_button_text(bg,title,title_width,very_light_color,dark_color,font); forget(write_image_to_JPEG_file(to_JPEG(bg),path,100)); result. define Printable_tree popup_close_button ( CommonInfo cinfo, RGB color, String div_name, String state_var_name, ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then with xxxx = web_arg_encode(sha1(color)), path_on = site_directory+"/public/buttons/c"+xxxx+"_on.jpg", path_off = site_directory+"/public/buttons/c"+xxxx+"_off.jpg", result = (Printable_tree)["\"button\""], if file_exists(path_on) then result else with col = to_rgba(color), title = "x", title_width = printed_text_width(font,title), bg_on = create_button_background(lighten(col,30),20,20), bg_off = create_button_background(col,20,20), very_light_color = lighten(col,70), dark_color = darken(col,40), draw_button_text(bg_on,title,title_width,very_light_color,dark_color,font); draw_button_text(bg_off,title,title_width,very_light_color,dark_color,font); forget(write_image_to_JPEG_file(to_JPEG(bg_on),path_on,100)); forget(write_image_to_JPEG_file(to_JPEG(bg_off),path_off,100)); result. */ // The function below formats a datum of type 'HTML_Any($T)'. define Printable_tree format ( CommonInfo cinfo, String sn, // state_name Var(Int32) ic_v, HTML_Any($T) element, $T -> Printable_tree format_element, // able to format a datum of type $T Bool is_https, ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then if element is { any_text(opts,t) then ["",t,""], any_preformated(opts,s) then ["
",s,"
"], //["
",s,"
"], any_paragraph(opts,e) then ["

",format_element(e),"

\n"], any_image(url) then ["\"",url,"\""], any_image(url,w,h) then ["\"",url,"\""], any_table(opts,h_row, rows) then ["", format(h_row,format_element), format(rows,format_element),"
\n"], any_center(e) then ["
",format_element(e),"
"], any_mail_to(email,elem) then ["",format_element(elem),""], any_scroller(w,h,cw,ch,c) then format_scroller(sn,w,h,cw,ch,new_idnum(ic_v),c,format_element), any_fixed_size(w,h,c) then with url = create_secondary_document(site_directory,secret,sn,format_element,c,w), ["", "secondary document", ""], any_fixed_size_2(w,h,fn) then with url = fn+"?zauth="+make_authorization(site_directory,secret, fn), ["", "secondary document", ""], any_actioner(c,t,a,an,eo,ja,fn) then format_actioner(cinfo,sn,c,t,a,an,eo,ja,fn,is_https), any_foreign_link(options,url) then [""], any_foreign_link(options,url,name) then ["",name,""], any_private_download(url,name,extra_ext,action) then format_private_download(cinfo,sn,url,name,extra_ext,action), any_div(options, e) then [format_div_option(options), format_element(e),"\n"], any_div_empty(options) then [format_div_option(options), "\n"], any_coreattrs(attributs) then [format_attrs(attributs)] }. // *** [5.8] Formating 'in form' elements. define Printable_tree format ( CommonInfo cinfo, String fn, // form_name String sn, // state_name Var(Int32) ic_v, // idnum counter variable HTML_In_Form element, Bool is_https, ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then with format_element = (HTML_In_Form e) |-> format(cinfo,fn,sn,ic_v,e,is_https), if element is { literal_pt(t) then t, literal(t) then [t], sequence(l) then flat(map(format_element,l)) text(opts,t) then format(cinfo,sn,ic_v,any_text(opts,t),format_element,is_https), preformated(o,s) then format(cinfo,sn,ic_v,any_preformated(o,s),format_element,is_https), paragraph(opts,t) then format(cinfo,sn,ic_v,any_paragraph(opts,t),format_element,is_https), image(url) then format(cinfo,sn,ic_v,any_image(url),format_element,is_https), image(url,w,h) then format(cinfo,sn,ic_v,any_image(url,w,h),format_element,is_https), table(opts,header_row,rows) then format(cinfo,sn,ic_v,any_table(opts, header_row, rows),format_element,is_https), center(e) then format(cinfo,sn,ic_v,any_center(e),format_element,is_https), mail_to(a,e) then format(cinfo,sn,ic_v,any_mail_to(a,e),format_element,is_https), scroller(w,h,cw,ch,c) then format(cinfo,sn,ic_v,any_scroller(w,h,cw,ch,c),format_element,is_https), actioner(c,t,a,an,eo,ja) then format(cinfo,sn,ic_v,any_actioner(c,t,a,an,eo,ja,success(fn)),format_element,is_https), foreign_link(options,url) then format(cinfo,sn,ic_v,any_foreign_link(options,url),format_element,is_https), foreign_link(options,url,name) then format(cinfo,sn,ic_v,any_foreign_link(options,url,name),format_element,is_https), private_download(url,name,extra,action) then format(cinfo,sn,ic_v,any_private_download(url,name,extra,action),format_element,is_https), text_input(options, label_text, id, name, i, w) then [ "", ""], text_input_ro(options, label_text, id, name,i,w) then [ "", ""], password_input(options, label_text, id, name,i,w) then [ "", ""], text_area(opts,n,i,w,h) then [""], file_upload(label, id, n, w) then ["", ""], selector(opts, label, id, n,s,cs) then ["", ""], selector(opts, label, id, n,s,cs,sd) then ["", ""], selector_c(opts, label, id, n,s,cs) then ["", ""], selector_c(opts, label, id, n,s,cs,sd) then ["", ""], radio_button(options, label_text, id, n, v, c) then [ "", ""], radio_button_r(options, label_text, id, n, v, c) then [ "", ""], check_box(options, label_text, id, n, v, c) then [ if length(label_text) > 0 then [""] else [""], ""] check_box_r(options, label_text, id, n, v, c) then [ "", if length(label_text) > 0 then [""] else [""] ] div(options, e) then format(cinfo,sn,ic_v,any_div(options, e),format_element,is_https), div_empty(options) then format(cinfo,sn,ic_v,any_div_empty(options),format_element,is_https), hidden(name, value) then [""], }. *** [5.9] Formating 'off form' elements. The encryption type 'multipart/form-data' is required for a form containing an upload. define Bool contains_an_upload ( HTML_In_Form form_content ). define Bool contains_an_upload ( HTML_Row(HTML_In_Form) row ) = mapor(contains_an_upload, map(content,cells(row))). define Bool contains_an_upload ( HTML_In_Form form_content ) = if form_content is { literal_pt(t) then false, literal(t) then false, sequence(l) then mapor(contains_an_upload,l) text(o,t) then false, preformated(o,s) then false, paragraph(o,t) then false, image(u) then false, image(u,w,h) then false, table(o,_,rows) then mapor(contains_an_upload,rows), center(e) then contains_an_upload(e), mail_to(m,e) then false, // 'e' may but should not contain an upload scroller(w,h,cw,ch,e) then contains_an_upload(e), actioner(c,t,a,an,eo,ja) then false, foreign_link(o,u) then false, foreign_link(o,u,n) then false, private_download(p,n,e,a) then false, text_input(o,lt,l,n,i,w) then false, text_input_ro(o,lt,l,n,i,w) then false, password_input(o,lt,l,n,i,w) then false, text_area(o,n,i,w,h) then false, file_upload(l,i,n,w) then true, selector(o,l,i,n,s,c) then false, selector(o,l,i,n,s,c,p) then false, selector_c(o,l,i,n,s,c) then false, selector_c(o,l,i,n,s,c,p) then false, radio_button(o,_,_,n,v,c) then false, radio_button_r(o,_,_,n,v,c) then false, check_box(o,_,_,n,v,c) then false, check_box_r(o,_,_,n,v,c) then false, div(o,c) then contains_an_upload(c), div_empty(o) then false, hidden(_,_) then false }. define String enctype ( HTML_In_Form form_content ) = if contains_an_upload(form_content) then " enctype=multipart/form-data" else "". define Printable_tree format ( CommonInfo cinfo, String sn, // state_name Var(Int32) ic_v, // 'idnum' counter variable HTML_Off_Form element, Bool is_https, ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then with format_element = (HTML_Off_Form e) |-> format(cinfo,sn,ic_v,e,is_https), if element is { literal_pt(t) then t, literal(t) then [t], sequence(l) then flat(map(format_element,l)), text(opts,t) then format(cinfo,sn,ic_v,any_text(opts,t),format_element,is_https), preformated(o,s) then format(cinfo,sn,ic_v,any_preformated(o,s),format_element,is_https), paragraph(opts,t) then format(cinfo,sn,ic_v,any_paragraph(opts,t),format_element,is_https), image(url) then format(cinfo,sn,ic_v,any_image(url),format_element,is_https), image(url,w,h) then format(cinfo,sn,ic_v,any_image(url,w,h),format_element,is_https), table(opts,header_row, rows) then format(cinfo,sn,ic_v,any_table(opts,header_row, rows),format_element,is_https), center(e) then format(cinfo,sn,ic_v,any_center(e),format_element,is_https), mail_to(a,e) then format(cinfo,sn,ic_v,any_mail_to(a,e),format_element,is_https), scroller(w,h,cw,ch,c) then format(cinfo,sn,ic_v,any_scroller(w,h,cw,ch,c),format_element,is_https), fixed_size(w,h,c) then format(cinfo,sn,ic_v,any_fixed_size(w,h,c),format_element,is_https), fixed_size_2(w,h,fn) then format(cinfo,sn,ic_v,any_fixed_size_2(w,h,fn),format_element,is_https), actioner(c,t,a,an,eo,ja) then format(cinfo,sn,ic_v,any_actioner(c,t,a,an,eo,ja,failure),format_element,is_https), actioner(c,t,a,an,eo,ja,fn) then format(cinfo,sn,ic_v,any_actioner(c,t,a,an,eo,ja,success(fn)),format_element,is_https), foreign_link(options,url) then format(cinfo,sn,ic_v,any_foreign_link(options,url),format_element,is_https), foreign_link(options,url,name) then format(cinfo,sn,ic_v,any_foreign_link(options,url,name),format_element,is_https), private_download(url,name,extra,action) then format(cinfo,sn,ic_v,any_private_download(url,name,extra,action),format_element,is_https), label(n) then [""], form(fn,attributs, c) then [ "
", // action is set dynamically by // the actioner using JavaScript format(cinfo,fn,sn,ic_v,c,is_https), "
" ] div(options, e) then format(cinfo,sn,ic_v,any_div(options, e),format_element,is_https), div_empty(options) then format(cinfo,sn,ic_v,any_div_empty(options),format_element,is_https), }. *** [5.10] Formating meta-tags. define Printable_tree format_keywords ( List(String) l ) = if l is { [] then [ ], [h . t] then if t is [] then [h] else [h , ", " . format_keywords(t)] }. define Printable_tree format ( CommonInfo cinfo, String state_name, HTML_Meta m, Bool is_https ) = if m is { keywords(l) then ["\n"], refresh(co,ta,an,delay) then ["\n"], meta(n,c) then ["\n"], http_equiv(n,c) then ["\n"], generic_meta(l) then [" if p is (n,v) then [n,"=\"",v,"\" "], l)), ">\n"], literal(s) then [s] }. define Printable_tree format ( CommonInfo cinfo, String state_name, List(HTML_Meta) metas, Bool is_https, String charset ) = if metas is { [] then [format(cinfo,state_name,http_equiv("content-type", "text/html; charset="+charset),is_https)], [h . t] then [format(cinfo,state_name,h,is_https) . format(cinfo,state_name,t,is_https,charset)] }. define Printable_tree format ( Body_Option o ) = if o is { background_color(c) then [" bgcolor=\"" , (String)html_format(c), "\""], background_image(n) then [" background=", n], background_image(n,o) then [" style=\"background: url(",n,")",format(o),"\""] }. define Printable_tree format ( List(Body_Option) l ) = if l is { [ ] then [ ], [h . t] then [format(h) . format(t)] }. define Printable_tree add_css_files ( List(CSS_File) l ) = if l is { [ ] then [ ], [h . t] then [ ["\n" ] . add_css_files(t)] }. define Printable_tree add_js_files ( List(JS_File) l ) = if l is { [ ] then [ ], [h . t] then [ ["\n" ] . add_js_files(t)] }. define Printable_tree add_css_styles ( List(CSS_Style) css_styles ) = if css_styles is { [] then [], [_._] then [ "\n" ] }. define Printable_tree format ( CommonInfo cinfo, String state_name, HTML_Page page, Bool is_https, String charset ) = if cinfo is info(common_name,http_port,https_port,site_directory,secret) then with ic_v = var((Int32)0), if page is { html_page(title,metas,css_styles, css_files, js_files, body) then if body is body(options,element) then [ doctype_w3c_header, "\n", "\n", add_css_styles(css_styles), add_css_files(css_files), add_js_files(js_files), "\n", "\n", "",title,"\n", // put title format(cinfo,state_name,metas,is_https,charset), // format the metas "\n", "", // format body options //"
", format(cinfo,state_name,ic_v,element,is_https), //"
", "\n", "" ] }.