t_iweb.ml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. open Seppo_lib
  2. open Alcotest
  3. let set_up = "setup", `Quick, (fun () ->
  4. Mirage_crypto_rng_lwt.initialize (module Mirage_crypto_rng.Fortuna);
  5. Unix.chdir "../../../test/"
  6. )
  7. let tc_scanf = "tc_scanf", `Quick, (fun () ->
  8. Scanf.sscanf "37s" "%is" (fun i -> i)
  9. |> check int __LOC__ 37
  10. )
  11. let tc_regexp = "tc_regexp", `Quick, (fun () ->
  12. let rx = Str.regexp {|^[^\n\t ]\([^\n\t]+[^\n\t ]\)?$|} in
  13. assert (Str.string_match rx "a" 0);
  14. assert true
  15. )
  16. let tc_markup_xml = "tc_markup_xml", `Quick, (fun () ->
  17. [
  18. `Start_element (("", "foo"), []);
  19. `Start_element (("", "bar"), []);
  20. `Start_element (("", "baz"), []);
  21. `End_element;
  22. `End_element;
  23. `End_element;
  24. ]
  25. |> Markup.of_list |> Markup.pretty_print |> Markup.write_xml
  26. |> Markup.to_string
  27. |> check string __LOC__ "<foo>\n <bar>\n <baz/>\n </bar>\n</foo>\n"
  28. )
  29. let tc_redir_if_cgi_bin = "tc_redir_if_cgi_bin", `Quick, (fun () ->
  30. let r : Cgi.Request.t = {
  31. content_type = "text/plain";
  32. content_length = None;
  33. host = "example.com";
  34. http_cookie = "";
  35. path_info = "/shaarli";
  36. query_string = "post=uhu";
  37. request_method = "GET";
  38. remote_addr = "127.0.0.1";
  39. scheme = "https";
  40. script_name = "seppo.cgi";
  41. server_port = "443";
  42. raw_string = Sys.getenv_opt
  43. } in
  44. let assrt_redir request_uri exp =
  45. match exp , r |> Iweb.redir_if_cgi_bin ~request_uri with
  46. | Some exp, Error (`Found, h,_) -> h |> List.assoc "Location" |> check string __LOC__ exp
  47. | None, Ok _ -> ()
  48. | _ -> failwith __LOC__
  49. in
  50. assrt_redir "/cgi-bin/seppo.cgi" (Some "/seppo.cgi");
  51. assrt_redir "/cgi-bin/sub/seppo.cgi" (Some "/sub/seppo.cgi");
  52. assrt_redir "/seppo.cgi" None;
  53. assrt_redir "/sub/seppo.cgi" None;
  54. ()
  55. )
  56. let tc_login = "tc_login", `Quick, (fun () ->
  57. Iweb.ClientCookie.name |> check string __LOC__ "#session";
  58. let tit = {|> U " h & ' u <|}
  59. and tok = "ff13e7eaf9541ca2ba30fd44e864c3ff014d2bc9"
  60. and ret = "retu"
  61. and att n v = (("", n), v)
  62. and elm name atts = `Start_element (("", name), atts) in
  63. [
  64. `Xml
  65. {
  66. Markup.version = "1.0";
  67. encoding = Some "utf-8";
  68. standalone = Some false;
  69. };
  70. `PI
  71. ("xml-stylesheet", "type='text/xsl' href='./themes/current/do=login.xsl'");
  72. `Comment
  73. "\n\
  74. \ must be compatible with \
  75. https://code.mro.name/mro/Shaarli-API-test/src/master/tests/test-post.sh\n\
  76. \ \
  77. https://code.mro.name/mro/ShaarliOS/src/1d124e012933d1209d64071a90237dc5ec6372fc/ios/ShaarliOS/API/ShaarliCmd.m#L386\n";
  78. elm "html" [ att "xmlns" "http://www.w3.org/1999/xhtml" ];
  79. elm "head" [];
  80. elm "title" [];
  81. `Text [ tit ];
  82. `End_element;
  83. `End_element;
  84. elm "body" [];
  85. elm "form" [ att "method" "post" ];
  86. elm "input" [ att "name" "login"; att "type" "text" ];
  87. `End_element;
  88. elm "input" [ att "name" "password"; att "type" "password" ];
  89. `End_element;
  90. elm "input" [ att "name" "longlastingsession"; att "type" "checkbox" ];
  91. `End_element;
  92. elm "input" [ att "name" "token"; att "type" "hidden"; att "value" tok ];
  93. `End_element;
  94. elm "input" [ att "name" "returnurl"; att "type" "hidden"; att "value" ret ];
  95. `End_element;
  96. elm "input" [ att "value" "Login"; att "type" "submit" ];
  97. `End_element;
  98. `End_element;
  99. `End_element;
  100. ]
  101. |> Markup.of_list |> Markup.pretty_print |> Markup.write_xml
  102. |> Markup.to_string |> String.length
  103. |> check int __LOC__ 841
  104. )
  105. module ClientCookie = struct
  106. let tc_cookie = "tc_cookie", `Quick, (fun () ->
  107. Iweb.ClientCookie.name |> check string __LOC__ "#session";
  108. (match "5:seppi"
  109. |> Iweb.ClientCookie.decode with
  110. | Ok (Auth.Uid uid) ->
  111. uid |> check string __LOC__ "seppi"
  112. | Error e -> e |> check string __LOC__ "");
  113. Auth.Uid "seppa"
  114. |> Iweb.ClientCookie.encode
  115. |> check string __LOC__ "5:seppa";
  116. (match Auth.Uid "seppu"
  117. |> Iweb.ClientCookie.encode
  118. |> Iweb.ClientCookie.decode with
  119. | Ok Auth.Uid uid ->
  120. uid |> check string __LOC__ "seppu"
  121. | Error e -> e |> check string __LOC__ "");
  122. assert true
  123. )
  124. end
  125. module Form = struct
  126. let tc_of_string = "tc_frm", `Quick, (fun () ->
  127. let frm = {|token=237054ce-4c9c-4155-8c6b-7b79bdb1d139&id=https%3A%2F%2Fsocial.nlnet.nl%2Fusers%2Fgerben&inbox=https%3A%2F%2Fsocial.nlnet.nl%2Fusers%2Fgerben%2Finbox&%7Eis_subscriber=no&%7Eam_subscribed_to=pending&%7Eis_blocked=no|}
  128. |> Html.Form.of_string in
  129. frm |> List.length |> check int __LOC__ 6;
  130. frm |> List.assoc "token" |> List.hd |> check string __LOC__ "237054ce-4c9c-4155-8c6b-7b79bdb1d139";
  131. frm |> List.assoc "id" |> List.hd |> check string __LOC__ {|https://social.nlnet.nl/users/gerben|};
  132. frm |> List.assoc "inbox" |> List.hd |> check string __LOC__ {|https://social.nlnet.nl/users/gerben/inbox|};
  133. frm |> List.assoc "~is_subscriber" |> List.hd |> check string __LOC__ {|no|};
  134. frm |> List.assoc "~am_subscribed_to" |> List.hd |> check string __LOC__ {|pending|};
  135. frm |> List.assoc "~is_blocked" |> List.hd |> check string __LOC__ {|no|};
  136. ();
  137. let frm = {|token=65fed285-a489-4e3f-9f2a-4a896e4f14ce&id=https%3A%2F%2Fbewegung.social%2Fusers%2Fmro&inbox=https%3A%2F%2Fbewegung.social%2Fusers%2Fmro%2Finbox&%7Eis_subscriber=yes&%7Eam_subscribed_to=no&%7Eis_blocked=no&am_subscribed_to=on&is_subscriber=on|}
  138. |> Html.Form.of_string in
  139. frm |> List.length |> check int __LOC__ 8;
  140. frm |> List.assoc "token" |> List.hd |> check string __LOC__ "65fed285-a489-4e3f-9f2a-4a896e4f14ce";
  141. frm |> List.assoc "id" |> List.hd |> check string __LOC__ {|https://bewegung.social/users/mro|};
  142. frm |> List.assoc "inbox" |> List.hd |> check string __LOC__ {|https://bewegung.social/users/mro/inbox|};
  143. frm |> List.assoc "~is_subscriber" |> List.hd |> check string __LOC__ {|yes|};
  144. frm |> List.assoc "~am_subscribed_to" |> List.hd |> check string __LOC__ {|no|};
  145. frm |> List.assoc "~is_blocked" |> List.hd |> check string __LOC__ {|no|};
  146. frm |> List.assoc "is_subscriber" |> List.hd |> check string __LOC__ {|on|};
  147. frm |> List.assoc "am_subscribed_to" |> List.hd |> check string __LOC__ {|on|};
  148. ()
  149. )
  150. end
  151. let tc_date = "tc_date", `Quick, (fun () ->
  152. let d x = x
  153. |> Option.value ~default:Iweb.Post.epoch_shaarli
  154. |> Ptime.to_rfc3339
  155. in
  156. "20230927_125036" |> Iweb.Post.s2d |> d |> check string __LOC__ "2023-09-27T12:50:36-00:00"
  157. )
  158. let tc_bookmarklet = "tc_bookmarklet", `Quick, (fun () ->
  159. let s = Option.value ~default:"" in
  160. let b s = if s then "yes" else "no" in
  161. let d s = s |> Option.value ~default:Ptime.min |> Ptime.to_rfc3339 in
  162. let u x = x |> Option.value ~default:Uri.empty |> Uri.to_string in
  163. let s' x= x in
  164. let l = String.concat " " in
  165. let now = ((2023,9,27),((14,45,42),2*60*60))
  166. |> Ptime.of_date_time in
  167. let emp = Iweb.Post.empty in
  168. let emp = {emp with dat = now} in
  169. let x = {|post=https%3A%2F%2Fwww.heise.de%2F&source=bookmarklet&scrape=no&title=heise+online+-+IT-News%2C+Nachrichten+und+Hintergr%C3%BCnde&tags=heise+online%2C+c%27t%2C+iX%2C+MIT+Technology+Review%2C+Newsticker%2C+Telepolis%2C+Security%2C+Netze&image=https%3A%2F%2Fheise.cloudimg.io%2Fbound%2F1200x1200%2Fq85.png-lossy-85.webp-lossy-85.foil1%2F_www-heise-de_%2Ficons%2Fho%2Fopengraph%2Fopengraph.png&description=News+und+Foren+zu+Computer%2C+IT%2C+Wissenschaft%2C+Medien+und+Politik.+Preisvergleich+von+Hardware+und+Software+sowie+Downloads+bei+Heise+Medien.|} in
  170. let r : Iweb.Post.t = x
  171. |> Uri.query_of_encoded
  172. |> List.fold_left Iweb.Post.sift_bookmarklet_get emp in
  173. r.scrape |> b |> check string __LOC__ "yes";
  174. r.source |> s |> check string __LOC__ "bookmarklet";
  175. r.dat |> d |> check string __LOC__ "2023-09-27T12:45:42-00:00";
  176. r.url |> u |> check string __LOC__ "https://www.heise.de/";
  177. r.tit |> s |> check string __LOC__ "heise online - IT-News, Nachrichten und Hintergründe";
  178. r.dsc |> s |> check string __LOC__ "News und Foren zu Computer, IT, Wissenschaft, Medien und Politik. Preisvergleich von Hardware und Software sowie Downloads bei Heise Medien.";
  179. r.tag |> l |> check string __LOC__ "heise online, c't, iX, MIT Technology Review, Newsticker, Telepolis, Security, Netze";
  180. r.pri |> b |> check string __LOC__ "no";
  181. assert (r.sav |> Option.is_none);
  182. r.can |> s |> check string __LOC__ "";
  183. r.tok |> s'|> check string __LOC__ "";
  184. r.ret |> u |> check string __LOC__ "";
  185. r.img |> u |> check string __LOC__ "https://heise.cloudimg.io/bound/1200x1200/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/icons/ho/opengraph/opengraph.png";
  186. let x = {|post=Some #text 🐫|} in
  187. let r : Iweb.Post.t = x
  188. |> Uri.query_of_encoded
  189. |> List.fold_left Iweb.Post.sift_bookmarklet_get emp in
  190. r.scrape |> b |> check string __LOC__ "no";
  191. r.source |> s |> check string __LOC__ "";
  192. r.dat |> d |> check string __LOC__ "2023-09-27T12:45:42-00:00";
  193. r.url |> u |> check string __LOC__ "";
  194. r.tit |> s |> check string __LOC__ "Some #text 🐫";
  195. r.dsc |> s |> check string __LOC__ "";
  196. r.tag |> l |> check string __LOC__ "";
  197. r.pri |> b |> check string __LOC__ "no";
  198. assert (r.sav |> Option.is_none);
  199. r.can |> s |> check string __LOC__ "";
  200. r.tok |> s'|> check string __LOC__ "";
  201. r.ret |> u |> check string __LOC__ "";
  202. r.img |> u |> check string __LOC__ ""
  203. )
  204. let tc_post = "tc_post", `Quick, (fun () ->
  205. let x = "?lf_linkdate=20210913_134542&token=f19a65cecdfa2971afb827bc9413eb7244e469a8&returnurl=&lf_image=&lf_url=http://example.com&lf_title=title&lf_description=body%20%23tags&save_edit=Save" in
  206. let s = Option.value ~default:"" in
  207. let b s = if s then "yes" else "no" in
  208. let d s = s |> Option.value ~default:Iweb.Post.epoch_shaarli |> Ptime.to_rfc3339 in
  209. let u x = x |> Option.value ~default:Uri.empty |> Uri.to_string in
  210. let l = String.concat " " in
  211. let s' x = x in
  212. let r : Iweb.Post.t = x
  213. |> Uri.of_string
  214. |> Uri.query
  215. |> List.fold_left Iweb.Post.sift_post Iweb.Post.empty in
  216. r.scrape |> b |> check string __LOC__ "no";
  217. r.source |> s |> check string __LOC__ "";
  218. r.dat |> d |> check string __LOC__ "2021-09-13T13:45:42-00:00";
  219. r.url |> u |> check string __LOC__ "http://example.com";
  220. r.tit |> s |> check string __LOC__ "title";
  221. r.dsc |> s |> check string __LOC__ "body #tags";
  222. r.tag |> l |> check string __LOC__ "";
  223. r.pri |> b |> check string __LOC__ "no";
  224. (match r.sav with | Some Save -> "Save"| _ -> "Fail") |> check string __LOC__ "Save";
  225. r.can |> s |> check string __LOC__ "";
  226. r.tok |> s'|> check string __LOC__ "f19a65cecdfa2971afb827bc9413eb7244e469a8";
  227. r.ret |> u |> check string __LOC__ "";
  228. r.img |> u |> check string __LOC__ ""
  229. )
  230. module Actor = struct
  231. let tc_basic = "tc_basic", `Quick, (fun () ->
  232. Logr.info (fun m -> m "%s.%s" "Iweb.Actor" "basic");
  233. let s = {|token=68f4cf03-8f2d-491c-a954-bd8118f93c01&id=https%3A%2F%2Falpaka.social%2Fusers%2Ftraunstein&inbox=https%3A%2F%2Falpaka.social%2Fusers%2Ftraunstein%2Finbox&~notify=no&~subscribe=yes&~block=no&notify=on|} in
  234. let f = s |> Html.Form.of_string in
  235. f |> List.length |> check int __LOC__ 7;
  236. f |> List.assoc "token" |> String.concat "|" |> check string __LOC__ "68f4cf03-8f2d-491c-a954-bd8118f93c01";
  237. f |> List.assoc "id" |> String.concat "|" |> check string __LOC__ "https://alpaka.social/users/traunstein";
  238. f |> List.assoc "inbox" |> String.concat "|" |> check string __LOC__ "https://alpaka.social/users/traunstein/inbox";
  239. f |> List.assoc "~notify" |> String.concat "|" |> check string __LOC__ "no";
  240. f |> List.assoc "~subscribe" |> String.concat "|" |> check string __LOC__ "yes";
  241. f |> List.assoc "~block" |> String.concat "|" |> check string __LOC__ "no";
  242. f |> List.assoc "notify" |> String.concat "|" |> check string __LOC__ "on";
  243. let switch k v' v =
  244. if v' = v
  245. then None
  246. else (
  247. Logr.debug (fun m -> m "field %s: %s" k (v |> As2.No_p_yes.to_string));
  248. Some k) in
  249. let form_switch_folder k_of_old f_switch form init (k_old,v_old) =
  250. match k_old |> k_of_old with
  251. | None -> init
  252. | Some k ->
  253. let v = match form |> List.assoc_opt k with
  254. | None
  255. | Some ["no"] -> As2.No_p_yes.No
  256. | _ -> As2.No_p_yes.Yes in
  257. let v_old = match v_old with
  258. | ["no"] -> As2.No_p_yes.No
  259. | _ -> As2.No_p_yes.Yes in
  260. match f_switch k v_old v with
  261. | None -> init
  262. | Some x -> x :: init in
  263. f
  264. |> List.fold_left (form_switch_folder (St.after ~prefix:"~") switch f) []
  265. |> String.concat "|"
  266. |> check string __LOC__ "subscribe|notify"
  267. )
  268. let tc_command = "tc_command", `Quick, (fun () ->
  269. let uuid = Uuidm.v `V4 in
  270. (match
  271. {|token=b346c8f4-c734-4504-922c-4a597cf3e7d3&id=https%3A%2F%2Fsocial.nlnet.nl%2Fusers%2Fgerben&inbox=https%3A%2F%2Fsocial.nlnet.nl%2Fusers%2Fgerben%2Finbox&%7Enotify=no&%7Esubscribed=pending&%7Eblocked=no|}
  272. |> Html.Form.of_string |> Iweb.Actor.command uuid with
  273. | `Unsubscribe -> ()
  274. | _ -> failwith __LOC__);
  275. (match
  276. {|token=65fed285-a489-4e3f-9f2a-4a896e4f14ce&id=https%3A%2F%2Fbewegung.social%2Fusers%2Fmro&inbox=https%3A%2F%2Fbewegung.social%2Fusers%2Fmro%2Finbox&%7Eis_subscriber=yes&%7Eam_subscribed_to=no&%7Eis_blocked=no&am_subscribed_to=on&is_subscriber=on|}
  277. |> Html.Form.of_string |> Iweb.Actor.command uuid with
  278. | `Subscribe -> ()
  279. | _ -> failwith __LOC__)
  280. )
  281. end
  282. let tc_xhtml = "tc_xhtml", `Quick, (fun () ->
  283. let i_uid : Html.Form.input = ("setlogin", "text", [
  284. ("required","required");
  285. ("autofocus","autofocus");
  286. ("maxlength","50");
  287. ("minlength","1");
  288. ("pattern", {|^[a-zA-Z0-9_.\-]+$|});
  289. ("placeholder","Your local name as 'alice' in @alice@example.com");
  290. ]) in
  291. let x = Iweb.(xhtmlform ~clz:"clz" "a" "b" [i_uid] ["setlogin","strange"] [ n i_uid "uid" ]) in
  292. let b = Buffer.create 1024 in
  293. Xml.to_buf x b;
  294. b
  295. |> Buffer.contents
  296. |> check string __LOC__ {|<?xml version="1.0"?>
  297. <html xml:base="../" xmlns="http://www.w3.org/1999/xhtml">
  298. <head>
  299. <link rel="icon" type="image/jpg" href="../me-avatar.jpg"/>
  300. <meta name="generator" content="Seppo.Social"/>
  301. <title>a</title></head>
  302. <body>
  303. <form method="post" name="b" id="b" class="clz">
  304. <input name="setlogin" type="text" value="uid" placeholder="Your local name as 'alice' in @alice@example.com" pattern="^[a-zA-Z0-9_.\-]+$" minlength="1" maxlength="50" autofocus="autofocus" required="required" class="is-invalid"/>
  305. <div role="alert" data-for="setlogin">strange</div></form>
  306. </body>
  307. </html>|};
  308. ();
  309. match Html.Form.string_opt i_uid [ Iweb.n i_uid "u d" ] with
  310. | Error (f,v) ->
  311. f |> check string __LOC__ "setlogin";
  312. v |> check string __LOC__ "pattern mismatch"
  313. | Ok _ -> failwith __LOC__
  314. )
  315. let () =
  316. run
  317. "seppo_suite" [
  318. __FILE__ , [
  319. set_up;
  320. tc_scanf;
  321. tc_regexp;
  322. tc_markup_xml;
  323. tc_redir_if_cgi_bin;
  324. tc_login;
  325. ClientCookie.tc_cookie;
  326. Form.tc_of_string;
  327. tc_date;
  328. tc_bookmarklet;
  329. tc_post;
  330. Actor.tc_basic;
  331. Actor.tc_command;
  332. tc_xhtml;
  333. ]
  334. ]