osmol

A simple static Gopher server in OCaml
git clone git://git.luxferre.top/osmol.git
Log | Files | Refs | README | LICENSE

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!"