package github-unix

  1. Overview
  2. Docs

Source file github_cookie_jar.ml

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
(*
 * Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
 * Copyright (c) 2013 David Sheets <sheets@alum.mit.edu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *)
open Printf
open Lwt

type t = { jar_path : string }

exception InvalidName of string

let invalid_names = Re.(List.map compile [
  seq [bos; str "."];
  str "../";
  seq [bos; str Filename.dir_sep];
  seq [str Filename.dir_sep; eos];
])

let jar_path { jar_path } = jar_path

let file_kind_match path ~reg ~dir ~other = Lwt_unix.(
  stat path
  >>= fun { st_kind; _ } -> match st_kind with
    | S_REG -> reg ()
    | S_DIR -> dir ()
    | S_CHR | S_BLK | S_LNK | S_FIFO | S_SOCK -> other ()
)

let rec mkdir_p dir =
  match Sys.file_exists dir with
    | true -> return ()
    | false ->
        mkdir_p (Filename.dirname dir)
        >>= fun () -> Lwt_unix.mkdir dir 0o700

let rec init ?jar_path () =
  let jar_path = match jar_path with
    | None ->
        let home = try Sys.getenv "HOME" with Not_found -> "." in
        let basedir = Filename.concat home ".github" in
        Filename.concat basedir "jar"
    | Some jar_path -> jar_path
  in
  match Sys.file_exists jar_path with
    | true -> return { jar_path }
    | false ->
        printf "Github cookie jar: initialized %s\n" jar_path;
        mkdir_p jar_path
        >>= init ~jar_path

(* Save an authentication token to disk, under the [name]
 * file in the jar *)
let save ({ jar_path } as jar) ~name ~auth =
  (if List.exists (fun re -> Re.execp re name) invalid_names then
     fail (InvalidName name)
   else
     return ()
  ) >>= fun () ->
  let rec backup_path ?(dirok=false) name =
    let fullname = Filename.concat jar_path name in
    let backup () =
      let open Unix in
      let tm = gmtime (gettimeofday ()) in
      let backfname = sprintf "%s.%.4d%.2d%.2d.%2d%2d%2d.bak"
        name (1900 + tm.tm_year) (1 + tm.tm_mon) tm.tm_mday
        tm.tm_hour tm.tm_min tm.tm_sec in
      let fullback = Filename.concat jar_path backfname in
      printf "Github cookie jar: backing up\n%s -> %s\n" fullname fullback;
      Lwt_unix.rename fullname fullback
    in
    catch (fun () ->
      file_kind_match fullname
        ~reg:backup
        ~dir:(if dirok then return else backup)
        ~other:backup
    ) (function
      | Unix.Unix_error (Unix.ENOENT, _, _)
      | Unix.Unix_error (Unix.ENOTDIR, _, _) ->
        begin match Filename.dirname name with
        | "." -> return ()
        | parent -> backup_path ~dirok:true parent
        end
      | exn -> fail exn
    )
  in
  backup_path name
  >>= fun () ->
  let fullname = Filename.concat jar_path name in
  mkdir_p (Filename.dirname fullname)
  >>= fun () ->
  let auth_fd = Unix.(openfile fullname [O_CREAT; O_TRUNC; O_WRONLY] 0o600) in
  let auth_oc = Unix.out_channel_of_descr auth_fd in
  fprintf auth_oc "%s" (Github_j.string_of_auth auth);
  close_out auth_oc;
  printf "Github cookie jar: created %s\n" fullname;
  return jar

(* Delete an authentication token from disk, given the [name] in the jar *)
let delete jar ~name =
  if List.exists (fun re -> Re.execp re name) invalid_names then
    fail (InvalidName name)
  else
    Lwt_unix.unlink (Filename.concat jar.jar_path name)
    >>= fun () ->
    return jar

(* Read a JSON auth file in and parse it *)
let read_auth_file { jar_path } name =
  let fname = Filename.concat jar_path name in
  let { Unix.st_perm; _ } = Unix.stat fname in
  let safe_perm = 0o7770 land st_perm in
  begin if safe_perm <> st_perm
    then Unix.chmod fname safe_perm
  end;
  Lwt_io.with_file ~mode:Lwt_io.input fname
    (fun ic ->
       Lwt_stream.fold_s (fun b a -> return (a^b)) (Lwt_io.read_lines ic) ""
       >>= fun buf ->
       return (Github_j.auth_of_string buf)
    )

(* Retrieve all the cookies *)
let get_all ({ jar_path } as jar) =
  let rec traverse dir =
    let base = Filename.concat jar_path dir in
    let files = Lwt_unix.files_of_directory base in
    Lwt_stream.fold_s (fun b a ->
      if b = "." || b = ".." then return a else begin
        let path = Filename.concat base b in
        let ident = Filename.concat dir b in
        file_kind_match path
          ~reg:(fun () ->
            read_auth_file jar ident
            >>= fun auth ->
            return ((ident,auth)::a))
          ~dir:(fun () ->
            traverse ident
            >>= fun sub ->
            return (sub@a))
          ~other:(fun () -> return a)
    end
    ) files []
  in traverse ""

(* Get one cookie by name *)
let get jar ~name =
  catch (fun () ->
    read_auth_file jar name
    >>= fun auth ->
    return (Some auth)
  ) (fun _ -> return_none)
OCaml

Innovation. Community. Security.