package cohttp

  1. Overview
  2. Docs

Source file transfer_io.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
(*{{{ Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
 *
 * 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 Transfer

module Make (IO : S.IO) = struct
  open IO

  type reader = unit -> Transfer.chunk IO.t
  type writer = string -> unit IO.t

  module Chunked = struct
    let remaining_length chunk remaining =
      let read_len = Int64.of_int (String.length chunk) in
      Int64.sub remaining read_len

    let read_chunk ic size =
      let max_read_len = Int64.of_int 0x8000 in
      let len = min size max_read_len in
      read ic (Int64.to_int len)

    let rec junk_until_empty_line ic =
      read_line ic >>= function
      | None | Some "" -> return Done
      | Some _trailer -> junk_until_empty_line ic

    let read ~remaining ic () =
      (* read between 0 and 32Kbytes of a chunk *)
      let read_chunk_fragment () =
        read_chunk ic !remaining >>= fun chunk ->
        remaining := remaining_length chunk !remaining;
        (if !remaining = 0L (* End_of_chunk *) then read_line ic
           (* Junk the CRLF at end of chunk *)
         else return None)
        >>= fun _ -> return chunk
      in
      if !remaining = 0L then
        (* Beginning of a chunk: read chunk size, read up to 32K bytes *)
        read_line ic >>= function
        | None -> return Done
        | Some chunk_size_hex -> (
            match
              Http.Private.Parser.parse_chunk_length (chunk_size_hex ^ "\r\n")
            with
            | Error Partial ->
                assert false
                (* this branch will never be reached here since we feed the full line *)
            | Error (Msg _) -> return Done
            | Ok (0L, _consumed) ->
                (* TODO: Trailer header support *)
                junk_until_empty_line ic
            | Ok (count, _consumed) -> (
                remaining := count;
                read_chunk_fragment () >>= function
                | "" -> return Done (* 0 bytes read means EOF *)
                | buf -> return (Chunk buf)))
      else
        (* Middle of a chunk, read up to 32K bytes *)
        read_chunk_fragment () >>= function
        | "" -> return Done (* 0 bytes read means EOF *)
        | buf -> return (Chunk buf)

    let write oc buf =
      let len = String.length buf in
      (* do NOT send empty chunks, as it signals the end of the
         chunked body *)
      if len <> 0 then
        write oc (Printf.sprintf "%x\r\n" len) >>= fun () ->
        write oc buf >>= fun () -> write oc "\r\n"
      else return ()
  end

  module Fixed = struct
    let read ~remaining ic () =
      (* TODO functorise string to a bigbuffer *)
      match !remaining with
      | 0L -> return Done
      | len -> (
          let max_read_len = Int64.of_int 0x8000 in
          let read_len = Int64.to_int (min len max_read_len) in
          read ic read_len >>= function
          | "" -> return Done
          | buf ->
              remaining :=
                Int64.sub !remaining (Int64.of_int (String.length buf));
              return
                (match !remaining with 0L -> Final_chunk buf | _ -> Chunk buf))

    (* TODO enforce that the correct length is written? *)
    let write = write
  end

  module Unknown = struct
    (* If we have no idea, then read until EOF (connection shutdown by
       the remote party). *)
    let read ic () =
      read ic 4096 >>= fun buf ->
      if buf = "" then return Done else return (Chunk buf)

    let write = write
  end

  let write_and_flush fn oc buf = fn oc buf >>= fun () -> IO.flush oc

  let make_reader = function
    | Chunked -> Chunked.read ~remaining:(ref 0L)
    | Fixed len -> Fixed.read ~remaining:(ref len)
    | Unknown -> Unknown.read

  let write_ignore_blank writer io s =
    if String.length s = 0 then return () else writer io s

  let make_writer ~flush mode =
    let write =
      match mode with
      | Chunked -> Chunked.write
      | Fixed _ -> Fixed.write
      | Unknown -> Unknown.write
    in
    match flush with
    | false -> write
    | true -> write_and_flush write |> write_ignore_blank

  let read reader = reader ()
  let write writer buf = writer buf
end
OCaml

Innovation. Community. Security.