cgi.ml 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * Copyright (C) The #Seppo contributors. All rights reserved.
  12. *
  13. * This program is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU General Public License as published by
  15. * the Free Software Foundation, either version 3 of the License, or
  16. * (at your option) any later version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. *)
  26. module Request = struct
  27. (** https://tools.ietf.org/html/rfc3875#section-4.1.13 *)
  28. type t = {
  29. content_type : string;
  30. content_length : int option;
  31. host : string;
  32. http_cookie : string;
  33. path_info : string;
  34. query_string : string;
  35. remote_addr : string;
  36. request_method : string;
  37. scheme : string;
  38. script_name : string;
  39. server_port : string;
  40. raw_string : string -> string option
  41. }
  42. (** Almost trivial. https://tools.ietf.org/html/rfc3875
  43. * Does no parsing or conversion. *)
  44. let from_env () =
  45. try
  46. let env ?(default = "") s = s |> Sys.getenv_opt |> Option.value ~default in
  47. let r : t = {
  48. content_type = "CONTENT_TYPE" |> env;
  49. content_length = Option.bind
  50. ("CONTENT_LENGTH" |> Sys.getenv_opt)
  51. (fun s -> Option.bind
  52. (s |> int_of_string_opt)
  53. Option.some);
  54. host = "HTTP_HOST" |> env ~default:("SERVER_NAME" |> Sys.getenv);
  55. http_cookie = "HTTP_COOKIE" |> env ~default:"";
  56. path_info = "PATH_INFO" |> env ~default:"";
  57. query_string = "QUERY_STRING" |> env ~default:"";
  58. request_method = "REQUEST_METHOD"|>Sys.getenv;
  59. remote_addr = "REMOTE_ADDR" |> Sys.getenv;
  60. (* request_uri = "REQUEST_URI" |> Os.getenv ; *)
  61. scheme =
  62. (match "HTTPS" |> env with
  63. | "on" -> "https"
  64. | _ -> "http");
  65. script_name = "SCRIPT_NAME" |> Sys.getenv;
  66. server_port = "SERVER_PORT" |> Sys.getenv;
  67. raw_string = Sys.getenv_opt (* mybe we should limit and HTTP_ prefix the names *)
  68. }
  69. in Ok r
  70. with Not_found -> Error "Not Found."
  71. (** set script and path for a query_string. *)
  72. let path_and_query req =
  73. req.script_name ^ req.path_info
  74. ^ match req.query_string with
  75. | "" -> ""
  76. | qs -> "?" ^ qs
  77. let srvr r : string =
  78. let prt = match r.scheme, r.server_port with
  79. | "http", "80"
  80. | "https", "443" -> ""
  81. | _ -> ":" ^ r.server_port in
  82. r.scheme ^ "://" ^ r.host ^ prt
  83. let base r : Uri.t =
  84. let r = ((srvr r) ^ match r.script_name |> Filename.dirname with
  85. | "/" as s -> s
  86. | s -> s ^ "/") |> Uri.of_string in
  87. assert (r |> Uri.to_string |> St.ends_with ~suffix:"/");
  88. assert (not (r |> Uri.path |> St.ends_with ~suffix:"//"));
  89. r
  90. let abs r : string =
  91. (srvr r) ^ r.script_name ^ r.path_info ^ "?" ^ r.query_string
  92. (** despite https://tools.ietf.org/html/rfc3875#section-4.1.13 1und1.de
  93. * webhosting returns the script_name instead an empty or nonex path_info in
  94. * case *)
  95. let consolidate req' =
  96. Result.bind req' (fun (req : t) ->
  97. if String.equal req.path_info req.script_name
  98. then Ok {req with path_info = ""}
  99. else req')
  100. end
  101. module Response = struct
  102. type t = Cohttp.Code.status_code * (string * string) list * (out_channel -> unit)
  103. (* for ease of railway processing we use this strange type *)
  104. type t' = (t, t) result
  105. let body b oc = output_string oc b
  106. let nobody = St.camel |> body
  107. let flush uuid oc ((status, hdrs, f_body) : t) : int =
  108. Logr.debug (fun m -> m "%s.%s %a" "Cgi.Response" "flush" Uuidm.pp uuid);
  109. let single (k, v) = Printf.fprintf oc "%s: %s\r\n" k v in
  110. ("Status", status |> Cohttp.Code.string_of_status) |> single;
  111. hdrs |> List.iter single;
  112. ("X-Request-Id", uuid |> Uuidm.to_string) |> single;
  113. Printf.fprintf oc "\r\n";
  114. f_body oc;
  115. flush oc;
  116. match status with
  117. | #Cohttp.Code.server_error_status -> 1
  118. | _ -> 0
  119. end