webfinger.ml 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. let to_result none = Option.to_result ~none
  27. let ( >>= ) = Result.bind
  28. let ( let* ) = Result.bind
  29. let chain a b =
  30. let f a = Ok (a, b) in
  31. Result.bind a f
  32. let writev oc j =
  33. Ezjsonm.value_to_channel ~minify:false oc j;
  34. Ok ""
  35. let dir = ".well-known/webfinger"
  36. let well_known_uri (Rfc7565.T u' as u) =
  37. let host = u' |> Uri.host in
  38. assert (host |> Option.is_some);
  39. let host = host |> Option.get in
  40. let path = "/" ^ dir in
  41. Uri.make
  42. ~scheme:"https"
  43. ~host
  44. ~path
  45. ~query:["resource", [u |> Rfc7565.to_string]]
  46. ()
  47. let apa = "activitypub/actor.jsa" (* redeclare Ap.proj to avoid dependency cycle *)
  48. (* https://tools.ietf.org/html/rfc7033
  49. *)
  50. module Client = struct
  51. let http_get
  52. ?(key = None)
  53. (w : Uri.t) =
  54. let mape (_ : Ezjsonm.value Decoders__Error.t) =
  55. Logr.err (fun m -> m "%s: webfinger decode failed %a" E.e1027
  56. Uri.pp w);
  57. E.e1027 ^ ": webfinger decode failed" in
  58. let deco (_,j) = j
  59. |> As2_vocab.Decode.Webfinger.query_result
  60. |> Result.map_error mape in
  61. let headers = [Http.H.acc_app_jrd] |> Cohttp.Header.of_list in
  62. let%lwt p = w |> Http.get_jsonv ~key ~headers Result.ok in
  63. p
  64. >>= deco
  65. |> Lwt.return
  66. end
  67. let make (Auth.Uid uid, base) : As2_vocab.Types.Webfinger.query_result =
  68. let host = base |> Uri.host |> Option.value ~default:"-" in
  69. let subject = Printf.sprintf "%s:%s@%s" Rfc7565.scheme uid host in
  70. let tmpl = Format.asprintf "%a%s/%s?id={uri}" Uri.pp base Cfg.seppo_cgi "activitypub/actor.xml" in
  71. let open As2_vocab.Types.Webfinger in
  72. let path = apa in
  73. let links = [
  74. Self (`ActivityJsonLd, Uri.make ~path ());
  75. ProfilePage (`Html, Uri.make ~path:"." ());
  76. Alternate (`Atom, Rfc4287.defa);
  77. OStatusSubscribe tmpl;
  78. ] in
  79. {subject;aliases=[];links}
  80. let jsonm (uid, base) : (Ezjsonm.value,'a) result =
  81. (uid, base)
  82. |> make
  83. |> As2_vocab.Encode.Webfinger.query_result ~base
  84. |> Result.ok
  85. let target = dir ^ "/index.jrd"
  86. let rule : Make.t =
  87. { target;
  88. prerequisites = [ apa ];
  89. fresh = Make.Outdated;
  90. command = fun _ _ru _all ->
  91. File.out_channel_replace (fun oc ->
  92. Cfg.Base.(fn |> from_file)
  93. >>= chain Auth.(fn |> uid_from_file)
  94. >>= jsonm
  95. >>= writev oc)
  96. }
  97. let rulez = rule :: [] (* :: Ap.Person.rulez *)
  98. module Server = struct
  99. (* Create a local .well-known/webfinger and link here from the global one (in webroot). *)
  100. let target = dir ^ "/.htaccess"
  101. let rule : Make.t = {
  102. target;
  103. prerequisites = [ rule.target ];
  104. fresh = Make.Outdated;
  105. command = fun _pre _ _ ->
  106. File.out_channel_replace (fun oc ->
  107. let* (Auth.Uid uid),_ = Auth.(from_file fn) in
  108. let* base = Cfg.Base.(from_file fn) in
  109. let pat = base |> Uri.path in
  110. Printf.fprintf oc "# https://%s/S1002\n\
  111. # automatically linked or manually appended to <webroot>/%s\n\
  112. # created by ../../%s\n\
  113. RewriteEngine On\n\
  114. RewriteCond %%{QUERY_STRING} (?i)^(.+?&)?resource=%s:%s@.+$\n\
  115. RewriteRule ^$ %s%s/index.jrd [qsdiscard,last,redirect=seeother]\n"
  116. St.seppo_s target Cfg.seppo_cgi Rfc7565.scheme (uid |> Str.quote) pat dir;
  117. if not (pat |> String.equal "/")
  118. then (
  119. assert (pat |> St.is_prefix ~affix:"/");
  120. assert (pat |> St.is_suffix ~affix:"/");
  121. assert (target |> St.updir |> String.equal "../../");
  122. let prefi = pat |> St.updir in
  123. let dst = prefi ^ target in
  124. let _ = dst |> Filename.dirname |> File.mkdir_p File.pDir in
  125. if Unix.(try S_LNK == (lstat dst).st_kind
  126. with | _ -> false)
  127. then (
  128. Logr.debug (fun m -> m "%s.%s remove symlink %s" "Webfinger.Server" "rule" dst);
  129. try Unix.unlink dst
  130. with | e -> Logr.debug (fun m -> m "%s.%s couldn't remove %s: %a" "Make" "make" dst St.pp_exc e) )
  131. else
  132. Logr.warn (fun m -> m "%s.%s %s %s isn't a symlink, so I don't interfere with it. Do that manually." "Webfinger.Server" "rule" E.e1031 dst);
  133. let src = "../.." ^ pat ^ target in
  134. Logr.debug (fun m -> m "%s.%s ln -s %s %s" "Webfinger.Server" "rule" src dst);
  135. try Unix.symlink src dst
  136. with | e -> Logr.err (fun m -> m "%s.%s 3 %a" "Make" "make" St.pp_exc e)
  137. );
  138. Ok "")
  139. }
  140. let make = Make.make [rule]
  141. end