osmol.ml (2568B)
1 (* 2 OSmol: simple static Gopher server in OCaml 3 Uses the same logic as Gofor-LX in Python 4 Created by Luxferre in 2023, released into public domain 5 *) 6 7 let listen_port = ref "70" (* server port *) 8 let root_dir = ref "." (* content root directory *) 9 let index_map = ref "index.map" (* index map file name *) 10 let posargs = ref [] 11 let usage_msg = "Usage: " ^ Sys.argv.(0) ^ " [-p 70] [-d .] [-i index.map]" 12 let speclist = [ 13 ("-p", Arg.Set_string listen_port, "Port to listen on (default 70)"); 14 ("-d", Arg.Set_string root_dir, "Content root directory (default .)"); 15 ("-i", Arg.Set_string index_map, "Index map file name (default index.map)") 16 ] 17 let anon_fun parg = posargs := parg :: !posargs 18 let () = Arg.parse speclist anon_fun usage_msg 19 20 (* get the resolved root directory with no trailing slashes *) 21 22 let real_root_dir = try Unix.realpath !root_dir 23 with _ -> failwith "No root directory!" 24 25 (* request processing *) 26 27 (* server error message formatter *) 28 let errmsg msg = 29 Printf.sprintf "3%s\t\terror.host\t1\r\n.\r\n\r\n" msg |> Bytes.of_string 30 31 (* read a file as bytes sequence for Gopher output *) 32 let readfile path = 33 if Sys.file_exists path then (* read the file *) 34 let ch = open_in_bin path in 35 let len = in_channel_length ch in 36 let buf = Bytes.create len in 37 try (really_input ch buf 0 len; close_in_noerr ch; buf) 38 with _ -> errmsg "Error reading the selector, try again later!" 39 else errmsg "Selector not found!" 40 41 (* find content by selector and return it as bytes sequence *) 42 let findsel sel = 43 let selpath = (try Unix.realpath (real_root_dir ^ "/" ^ sel) 44 with _ -> "###") in 45 (* check if selpath starts with rootdir *) 46 if String.starts_with ~prefix:real_root_dir selpath then 47 if Sys.is_directory selpath then (* resolve to index *) 48 readfile (selpath ^ "/" ^ !index_map) 49 else readfile selpath (* normal file *) 50 else errmsg "Selector not found!" 51 52 (* inch is line-based, outch is binary *) 53 let reqproc inch outch = 54 let reqparts = input_line inch |> String.split_on_char '\t' in 55 output_bytes outch (match reqparts with 56 | sel :: [] -> findsel (String.trim sel) (* valid selector with no tab *) 57 | [] -> findsel "/" (* empty root selector *) 58 | _ :: _ -> errmsg "Search selectors unsupported!" 59 );; 60 61 (* start the server *) 62 63 let lport = int_of_string(!listen_port) in 64 if lport > 0 && lport < 65536 then ( 65 print_endline ("Starting server on port " ^ (string_of_int lport)); 66 Unix.establish_server reqproc (Unix.ADDR_INET (Unix.inet_addr_any, lport)) 67 ) else failwith "Invalid port!"