1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
(* Parsing known URLs from mo: and ic: URLs *)

(*
   For usage examples take a look at url_test.ml
*)

let checkbytes s : string =
  let buf = Buffer.create 4 in
  Buffer.add_int32_be buf (Lib.CRC.crc32 s); (* NB: big endian *)
  Buffer.contents buf

let rec group s =
  let open String in
  if length s <= 5 then s else
  sub s 0 5 ^ "-" ^ group (sub s 5 (length s - 5))

let encode_principal bytes : string =
  group (String.map Char.lowercase_ascii (Lib.Base32.encode (checkbytes bytes ^ bytes)))

(* Decode a principal according to https://sdk.dfinity.org/docs/interface-spec/index.html#textual-ids *)
let decode_principal principal : (string, string) result =
  let open Stdlib.String in

  if principal = "" then Error "principal cannot be empty" else
  let filtered =
    to_seq principal |>
      Seq.map Char.uppercase_ascii |>
      Seq.filter (fun c -> c >= '0' && c <= '9' || c >= 'A' && c <= 'Z') |>
      of_seq in
  match Lib.Base32.decode filtered with
  | Error e -> Error e
  | Ok bytes ->
    if length bytes < 4 then Error "principal too short" else
    let payload = sub bytes 4 (length bytes - 4) in
    let expected = encode_principal payload in
    if principal <> expected
    then Error (Printf.sprintf "invalid principal. Did you mean %S?" expected)
    else Ok payload

type parsed =
  | Package of (string * string)
  | Relative of string
  | Ic of string
  | IcAlias of string
  | Prim

let string_of_parsed = function
  | Package (x, y) -> Printf.sprintf "Package (%s, %s)" x y
  | Relative x -> Printf.sprintf "Relative %s" x
  | Ic x -> Printf.sprintf "Ic %s" x
  | IcAlias x -> Printf.sprintf "IcAlias %s" x
  | Prim -> "Prim"

let parse (f: string) : (parsed, string) result =
  match Lib.String.chop_prefix "mo:" f with
  | Some suffix ->
    begin match Stdlib.String.index_opt suffix '/' with
    | None ->
      if suffix = "prim" || suffix = "⛔"
      then Ok Prim
      else Ok (Package (suffix, ""))
    | Some i ->
      if suffix = "prim" || suffix = "⛔"
      then Error "The prim package has no modules"
      else
        let pkg = Stdlib.String.sub suffix 0 i in
        let path = Stdlib.String.sub suffix (i+1) (Stdlib.String.length suffix - (i+1)) in
        if Option.is_some (Lib.String.chop_prefix ".." (Lib.FilePath.normalise path))
        then Error (Printf.sprintf "Package imports musn't access parent directories: %s is invalid." path)
        else Ok (Package (pkg, path))
    end
  | None ->
    match Lib.String.chop_prefix "ic:" f with
    | Some principal -> begin match decode_principal principal with
      | Ok bytes -> Ok (Ic bytes)
      | Error err -> Error err
      end
    | None ->
      match Lib.String.chop_prefix "canister:" f with
      | Some suffix -> Ok (IcAlias suffix)
      | None ->
        begin match Stdlib.String.index_opt f ':' with
        | Some _ -> Error "Unrecognized URL"
        | None -> Ok (Relative (Lib.FilePath.normalise f))
        end


(* Basename of the IDL file searched (see DFX-Interface.md) *)
let idl_basename_of_blob bytes =
  encode_principal bytes ^ ".did"