]> matita.cs.unibo.it Git - helm.git/blob - helm/http_getter/http_getter.ml
04eaed9c44762189fea00f43199f9736b303f94e
[helm.git] / helm / http_getter / http_getter.ml
1 (*
2  *  Copyright (C) 2003, HELM Team.
3  *
4  *  This file is part of HELM, an Hypertextual, Electronic
5  *  Library of Mathematics, developed at the Computer Science
6  *  Department, University of Bologna, Italy.
7  *
8  *  HELM is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU General Public License
10  *  as published by the Free Software Foundation; either version 2
11  *  of the License, or (at your option) any later version.
12  *
13  *  HELM is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with HELM; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston,
21  *  MA  02111-1307, USA.
22  *
23  *  For details, see the HELM World-Wide-Web page,
24  *  http://cs.unibo.it/helm/.
25  *)
26
27 (* TODO optimization: precompile regexps *)
28
29 open Http_getter_common;;
30 open Http_getter_misc;;
31 open Http_getter_types;;
32 open Http_getter_debugger;;
33 open Printf;;
34
35   (* HTTP queries argument parsing *)
36
37 let parse_enc (req: Http_types.request) =
38   try
39     (match req#param "format" with
40     | "normal" -> Enc_normal
41     | "gz" -> Enc_gzipped
42     | s -> raise (Http_getter_bad_request ("Invalid format: " ^ s)))
43   with Http_types.Param_not_found _ -> Enc_normal
44 ;;
45 let parse_patch_dtd (req: Http_types.request) =
46   match req#param "patch_dtd" with
47   | s when String.lowercase s = "yes" -> true
48   | s when String.lowercase s = "no" -> false
49   | s -> raise (Http_getter_bad_request ("Invalid patch_dtd value: " ^ s))
50 ;;
51 let parse_output_format (req: Http_types.request) =
52   match req#param "format" with
53   | s when String.lowercase s = "txt" -> Fmt_text
54   | s when String.lowercase s = "xml" -> Fmt_xml
55   | s -> raise (Http_getter_bad_request ("Invalid /ls format: " ^ s))
56 ;;
57 let parse_ls_uri (req: Http_types.request) =
58   let baseuri = req#param "baseuri" in
59   let subs =
60     Pcre.extract ~pat:"^(\\w+):(.*)$" (Pcre.replace ~pat:"/+$"  baseuri)
61   in
62   match (subs.(1), subs.(2)) with
63   | "cic", uri -> Cic uri
64   | "theory", uri -> Theory uri
65   | _ -> raise (Http_getter_bad_request ("Invalid /ls baseuri: " ^ baseuri))
66 ;;
67
68   (* global maps, shared by all threads *)
69
70 let xml_map = new Http_getter_map.map Http_getter_env.xml_dbm in
71 let rdf_map = new Http_getter_map.map Http_getter_env.rdf_dbm in
72 let xsl_map = new Http_getter_map.map Http_getter_env.xsl_dbm in
73
74 let save_maps () = xml_map#close; rdf_map#close; xsl_map#close in
75 let map_of_uri = function
76   | uri when is_xml_uri uri -> xml_map
77   | uri when is_rdf_uri uri -> rdf_map
78   | uri when is_xsl_uri uri -> xsl_map
79   | uri -> raise (Http_getter_unresolvable_URI uri)
80 in
81 let resolve uri = (map_of_uri uri)#resolve uri in
82 let register uri =  (map_of_uri uri )#add uri in
83 let return_all_foo_uris map doctype filter outchan =
84   Http_daemon.send_basic_headers ~code:200 outchan;
85   Http_daemon.send_header "Content-Type" "text/xml" outchan;
86   Http_daemon.send_CRLF outchan;
87   output_string
88     outchan
89     (sprintf
90 "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>
91 <!DOCTYPE %s SYSTEM \"%s/getdtd?uri=%s.dtd\">
92
93 <%s>
94 "
95       doctype
96       Http_getter_env.my_own_url
97       doctype
98       doctype);
99   map#iter
100     (fun uri _ ->
101       if filter uri then
102         output_string outchan (sprintf "\t<uri value=\"%s\" />\n" uri));
103   output_string outchan (sprintf "</%s>\n" doctype)
104 in
105 let return_all_xml_uris = return_all_foo_uris xml_map "alluris" in
106 let return_all_rdf_uris = return_all_foo_uris rdf_map "allrdfuris" in
107 let return_ls =
108   let (++) (oldann, oldtypes, oldbody) (newann, newtypes, newbody) =
109     ((if newann   > oldann    then newann   else oldann),
110      (if newtypes > oldtypes  then newtypes else oldtypes),
111      (if newbody > oldbody    then newbody  else oldbody))
112    in
113   let basepart_RE =
114     Pcre.regexp "^([^.]*\\.[^.]*)((\\.body)|(\\.types))?(\\.ann)?"
115   in
116   let (types_RE, types_ann_RE, body_RE, body_ann_RE) =
117     (Pcre.regexp "\\.types", Pcre.regexp "\\.types.ann",
118      Pcre.regexp "\\.body", Pcre.regexp "\\.body.ann")
119   in
120   let (slash_RE, til_slash_RE, no_slashes_RE) =
121     (Pcre.regexp "/", Pcre.regexp "^.*/", Pcre.regexp "^[^/]*$")
122   in
123   fun lsuri fmt outchan ->
124     let pat =
125       "^" ^
126       (match lsuri with Cic p -> ("cic:" ^ p) | Theory p -> ("theory:" ^ p))
127     in
128     let (dir_RE, obj_RE) =
129       (Pcre.regexp (pat ^ "/"), Pcre.regexp (pat ^ "(\\.|$)"))
130     in
131     let dirs = ref StringSet.empty in
132     let objs = Hashtbl.create 17 in
133     let store_dir d =
134       dirs := StringSet.add (List.hd (Pcre.split ~rex:slash_RE d)) !dirs
135     in
136     let store_obj o =
137       let basepart = Pcre.replace ~rex:basepart_RE ~templ:"$1" o in
138       let oldflags =
139         try
140           Hashtbl.find objs basepart
141         with Not_found -> (false, No, No) (* no ann, no types no body *)
142       in
143       let newflags =
144         match o with
145         | s when Pcre.pmatch ~rex:types_RE s     -> (false, Yes, No)
146         | s when Pcre.pmatch ~rex:types_ann_RE s -> (true,  Ann, No)
147         | s when Pcre.pmatch ~rex:body_RE s      -> (false, No,  Yes)
148         | s when Pcre.pmatch ~rex:body_ann_RE s  -> (true,  No,  Ann)
149         | s -> (false, No, No)
150       in
151       Hashtbl.replace objs basepart (oldflags ++ newflags)
152     in
153     xml_map#iter  (* BLEARGH Dbm module lacks support for fold-like functions *)
154       (fun key _ ->
155         match key with
156         | uri when Pcre.pmatch ~rex:dir_RE uri ->  (* directory hit *)
157             let localpart = Pcre.replace ~rex:dir_RE uri in
158             if Pcre.pmatch ~rex:no_slashes_RE localpart then
159               store_obj localpart
160             else
161               store_dir localpart
162         | uri when Pcre.pmatch ~rex:obj_RE  uri ->  (* file hit *)
163             store_obj (Pcre.replace ~rex:til_slash_RE uri)
164         | uri -> () (* miss *));
165     match fmt with
166     | Fmt_text ->
167         let body =
168           (List.fold_left
169             (fun s d -> sprintf "%sdir, %s\n" s d) ""
170             (StringSet.elements !dirs)) ^
171           (Http_getter_misc.hashtbl_sorted_fold
172             (fun uri (annflag, typesflag, bodyflag) cont ->
173               sprintf "%sobject, %s, <%s,%s,%s>\n"
174                 cont uri (if annflag then "YES" else "NO")
175                 (string_of_ls_flag typesflag) (string_of_ls_flag bodyflag))
176             objs "")
177         in
178         Http_daemon.respond
179           ~headers:["Content-Type", "text/plain"] ~body outchan
180     | Fmt_xml ->
181         let body =
182           sprintf
183 "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>
184 <!DOCTYPE ls SYSTEM \"%s/getdtd?uri=ls.dtd\">
185
186 <ls>
187 %s
188 </ls>
189 "
190             Http_getter_env.my_own_url
191             ("\n" ^
192             (String.concat
193               "\n"
194               (List.map
195                 (fun d -> "<section>" ^ d ^ "</section>")
196                 (StringSet.elements !dirs))) ^ "\n" ^
197             (Http_getter_misc.hashtbl_sorted_fold
198               (fun uri (annflag, typesflag, bodyflag) cont ->
199                 sprintf
200 "%s<object name=\"%s\">
201 \t<ann value=\"%s\" />
202 \t<types value=\"%s\" />
203 \t<body value=\"%s\" />
204 </object>
205 "
206                   cont uri (if annflag then "YES" else "NO")
207                   (string_of_ls_flag typesflag)
208                   (string_of_ls_flag bodyflag))
209               objs ""))
210         in
211         Http_daemon.respond
212           ~headers:["Content-Type", "text/xml"] ~body outchan
213 in
214 let (index_line_sep_RE, index_sep_RE) =
215   (Pcre.regexp "[ \t]+", Pcre.regexp "\n+")
216 in
217   (* TODO support 'file://.*' servers *)
218 let update_from_server logmsg server_url = (* use global maps *)
219   debug_print ("Updating information from " ^ server_url);
220   let xml_url_of_uri = function
221       (* TODO missing sanity checks on server_url, e.g. it can contains $1 *)
222     | uri when (Pcre.pmatch ~pat:"^cic:" uri) ->
223         Pcre.replace ~pat:"^cic:" ~templ:server_url uri
224     | uri when (Pcre.pmatch ~pat:"^theory:" uri) ->
225         Pcre.replace ~pat:"^theory:" ~templ:server_url uri
226     | uri -> raise (Http_getter_invalid_URI uri)
227   in
228   let rdf_url_of_uri = function (* TODO as above *)
229     | uri when (Pcre.pmatch ~pat:"^helm:rdf.*//cic:" uri) ->
230         Pcre.replace ~pat:"^helm:rdf.*//cic:" ~templ:server_url uri
231     | uri when (Pcre.pmatch ~pat:"^helm:rdf.*//theory:" uri) ->
232         Pcre.replace ~pat:"^helm:rdf.*//theory:" ~templ:server_url uri
233     | uri -> raise (Http_getter_invalid_URI uri)
234   in
235   let log = ref (logmsg ^ "Processing server: " ^ server_url ^ "<br />\n") in
236   let (xml_index, rdf_index, xsl_index) =
237     (* TODO keeps index in memory, is better to keep them on temp files? *)
238     (http_get (server_url ^ "/" ^ Http_getter_env.xml_index),
239      http_get (server_url ^ "/" ^ Http_getter_env.rdf_index),
240      http_get (server_url ^ "/" ^ Http_getter_env.xsl_index))
241   in
242   if (xml_index = None && rdf_index = None && xsl_index = None) then
243     debug_print (sprintf "Warning: useless server %s" server_url);
244   (match xml_index with
245   | Some xml_index ->
246       (log := !log ^ "Updating XML db ...<br />\n";
247       List.iter
248         (fun l ->
249           try
250             (match Pcre.split ~rex:index_line_sep_RE l with
251             | [uri; "gz"] -> xml_map#add uri ((xml_url_of_uri uri) ^ ".xml.gz")
252             | [uri] -> xml_map#add uri ((xml_url_of_uri uri) ^ ".xml")
253             | _ -> log := !log ^ "Ignoring invalid line: " ^ l ^ "<br />\n")
254           with Http_getter_invalid_URI uri ->
255             log := !log ^ "Ignoring invalid XML URI: " ^ uri ^ "<br />\n")
256         (Pcre.split ~rex:index_sep_RE xml_index)) (* xml_index lines *)
257   | None -> ());
258   (match rdf_index with
259   | Some rdf_index ->
260       (log := !log ^ "Updating RDF db ...<br />\n";
261       List.iter
262         (fun l ->
263           try
264             (match Pcre.split ~rex:index_line_sep_RE l with
265             | [uri; "gz"] -> rdf_map#add uri ((rdf_url_of_uri uri) ^ ".xml.gz")
266             | [uri] -> rdf_map#add uri ((rdf_url_of_uri uri) ^ ".xml")
267             | _ -> log := !log ^ "Ignoring invalid line: " ^ l ^ "<br />\n")
268           with Http_getter_invalid_URI uri ->
269             log := !log ^ "Ignoring invalid RDF URI: " ^ uri ^ "<br />\n")
270         (Pcre.split ~rex:index_sep_RE rdf_index)) (* rdf_index lines *)
271   | None -> ());
272   (match xsl_index with
273   | Some xsl_index ->
274       (log := !log ^ "Updating XSLT db ...<br />\n";
275       List.iter
276         (fun l -> xsl_map#add l (server_url ^ "/" ^ l))
277         (Pcre.split ~rex:index_sep_RE xsl_index);
278       log := !log ^ "All done!<br />\n")
279   | None -> ());
280   debug_print "done with this server";
281   !log
282 in
283
284   (* thread action *)
285
286 let callback (req: Http_types.request) outchan =
287   try
288     debug_print ("Connection from " ^ req#clientAddr);
289     debug_print ("Received request: " ^ req#path);
290     (match req#path with
291     | "/help" -> return_html_msg Http_getter_const.usage_string outchan
292     | "/getxml" | "/getxslt" | "/getdtd" | "/resolve" | "/register" ->
293         (let uri = req#param "uri" in  (* common parameter *)
294         match req#path with
295         | "/getxml" ->
296             let enc = parse_enc req in
297             let patch_dtd =
298               try parse_patch_dtd req with Http_types.Param_not_found _ -> true
299             in
300             Http_getter_cache.respond_xml
301               ~url:(resolve uri) ~uri ~enc ~patch_dtd outchan
302         | "/getxslt" ->
303             let patch_dtd =
304               try parse_patch_dtd req with Http_types.Param_not_found _ -> true
305             in
306             Http_getter_cache.respond_xsl ~url:(resolve uri) ~patch_dtd outchan
307         | "/getdtd" ->
308             let patch_dtd =
309               try parse_patch_dtd req with Http_types.Param_not_found _ -> true
310             in
311             Http_getter_cache.respond_dtd
312               ~patch_dtd ~url:(Http_getter_env.dtd_dir ^ "/" ^ uri) outchan
313         | "/resolve" ->
314             (try
315               return_xml_msg
316                 (sprintf "<url value=\"%s\" />\n" (resolve uri))
317                 outchan
318             with Http_getter_unresolvable_URI uri ->
319               return_xml_msg "<unresolved />\n" outchan)
320         | "/register" ->
321             let url = req#param "url" in
322             register uri url;
323             return_html_msg "Register done" outchan
324         | _ -> assert false)
325     | "/update" ->
326         (xml_map#clear; rdf_map#clear; xsl_map#clear;
327         let log =
328           List.fold_left
329             update_from_server
330             ""  (* initial logmsg: empty *)
331               (* reverse order: 1st server is the most important one *)
332             (List.rev Http_getter_env.servers)
333         in
334         xml_map#sync; rdf_map#sync; xsl_map#sync;
335         return_html_msg log outchan)
336     | "/getalluris" ->
337         return_all_xml_uris
338           (fun uri ->
339             (Pcre.pmatch ~pat:"^cic:" uri) &&
340             not (Pcre.pmatch ~pat:"\\.types$" uri))
341           outchan
342     | "/getallrdfuris" ->
343         (let classs = req#param "class" in
344         try
345           let filter =
346             let base = "^helm:rdf:www\\.cs\\.unibo\\.it/helm/rdf/" in
347             match classs with
348             | ("forward" as c) | ("backward" as c) ->
349                 (fun uri -> Pcre.pmatch ~pat:(base ^ c) uri)
350             | c -> raise (Http_getter_invalid_RDF_class c)
351           in
352           return_all_rdf_uris filter outchan
353         with Http_getter_invalid_RDF_class c ->
354           raise (Http_getter_bad_request ("Invalid RDF class: " ^ c)))
355     | "/ls" -> return_ls (parse_ls_uri req) (parse_output_format req) outchan
356     | "/getempty" ->
357         Http_daemon.respond ~body:Http_getter_const.empty_xml outchan
358     | invalid_request ->
359         Http_daemon.respond_error ~status:(`Client_error `Bad_request) outchan);
360     debug_print "Done!\n"
361   with
362   | Http_types.Param_not_found attr_name ->
363       return_400 (sprintf "Parameter '%s' is missing" attr_name) outchan
364   | Http_getter_bad_request msg -> return_html_error msg outchan
365   | Http_getter_internal_error msg -> return_html_internal_error msg outchan
366   | Shell.Subprocess_error l ->
367       return_html_internal_error
368         (String.concat "<br />\n"
369           (List.map
370             (fun (cmd, code) ->
371               sprintf "Command '%s' returned %s"
372                 cmd (string_of_proc_status code))
373             l))
374         outchan
375   | exc ->
376       return_html_error
377         ("Uncaught exception: " ^ (Printexc.to_string exc))
378         outchan
379 in
380
381     (* daemon initialization *)
382
383 let main () =
384   Http_getter_env.dump_env ();
385   Unix.putenv "http_proxy" "";
386   at_exit save_maps;
387   Sys.catch_break true;
388   try
389     Http_daemon.start'
390       ~timeout:(Some 600) ~port:Http_getter_env.port ~mode:`Thread callback
391   with Sys.Break -> ()  (* 'save_maps' already registered with 'at_exit' *)
392 in
393
394 main ()
395