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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
module Stable = struct
open Core.Core_stable
module V1 = struct
type t = string [@@deriving sexp, bin_io, compare]
end
end
open Core
type t = string [@@deriving sexp_of, compare, hash]
type boundary = t
let of_string = Fn.id
let to_string = Fn.id
module Open = struct
let to_string_monoid t = String_monoid.concat_string [ "\n"; "--"; t; "\n" ]
end
module Close = struct
let to_string_monoid t = String_monoid.concat_string [ "\n"; "--"; t; "--" ]
end
module Open_first = struct
let to_string_monoid t = String_monoid.concat_string [ "--"; t; "\n" ]
end
let split t bstr =
let lf = Bigstring_shared.of_string "\n" in
let crlf = Bigstring_shared.of_string "\r\n" in
let dashdash = Bigstring_shared.of_string "--" in
let t = Bigstring_shared.of_string ("--" ^ t) in
let match_after ~pos bstr ~pattern =
let len = Bigstring_shared.length pattern in
Option.some_if
(pos >= 0
&& pos + len <= Bigstring_shared.length bstr
&& [%compare.equal: Bigstring_shared.t]
pattern
(Bigstring_shared.sub bstr ~pos ~len))
(pos + len)
in
let match_before ~pos:end_ bstr ~pattern =
let start = end_ - Bigstring_shared.length pattern in
match_after ~pos:start bstr ~pattern
|> Option.map ~f:(fun end_' ->
assert (end_ = end_');
start)
in
let match_crlf direction ~pos bstr =
if pos = 0 || pos = Bigstring_shared.length bstr
then Some pos
else (
let match_ =
match direction with
| `After -> fun pattern -> match_after ~pos bstr ~pattern
| `Before -> fun pattern -> match_before ~pos bstr ~pattern
in
Option.first_some (match_ crlf) (match_ lf))
in
let rec find_boundary pos =
match Bigstring_shared.substr_index bstr ~pos ~pattern:t with
| None ->
`Eof
| Some pos ->
let no_prologue = pos = 0 in
(match match_crlf `Before ~pos bstr with
| None ->
find_boundary (pos + 1)
| Some begin_ ->
let pos = pos + Bigstring_shared.length t in
let is_terminal, pos =
match match_after ~pos bstr ~pattern:dashdash with
| Some pos -> true, pos
| None -> false, pos
in
(match match_crlf `After ~pos bstr with
| None -> find_boundary pos
| Some end_ ->
if is_terminal
then `Close_boundary (begin_, pos)
else if no_prologue
then `Open_boundary_first end_
else `Open_boundary (begin_, end_)))
in
let rec loop pos acc has_prologue =
let sub ?stop () =
let stop = Option.value stop ~default:(Bigstring_shared.length bstr) in
let len = stop - pos in
if len <= 0 then Bigstring_shared.empty else Bigstring_shared.sub ~pos ~len bstr
in
match find_boundary pos with
| `Open_boundary_first pos -> loop pos acc false
| `Open_boundary (stop, pos) ->
let chunk = sub ~stop () in
loop pos (chunk :: acc) has_prologue
| `Close_boundary (stop, pos) ->
let chunk = sub ~stop () in
let epilogue =
if pos < Bigstring_shared.length bstr
then Some (Bigstring_shared.sub ~pos bstr)
else None
in
chunk :: acc, epilogue, has_prologue
| `Eof ->
let chunk = sub () in
chunk :: acc, None, has_prologue
in
let parts, epilogue, has_prologue = loop 0 [] true in
match List.rev parts with
| [] -> Some bstr, [], epilogue
| prologue :: parts when has_prologue -> Some prologue, parts, epilogue
| parts -> None, parts, epilogue
;;
module Generator = struct
type nonrec t = t Sequence.t
let sexp_of_t t =
[%sexp ((Sequence.take t 5 |> Sequence.to_list) @ [ "..." ] : string list)]
;;
let default =
Sequence.unfold ~init:0 ~f:(fun num ->
let str = sprintf "--==::BOUNDARY::%06d::==--" num in
Some (str, num + 1))
;;
let%expect_test _ =
Sequence.take default 5 |> Sequence.iter ~f:print_endline;
[%expect
{|
--==::BOUNDARY::000000::==--
--==::BOUNDARY::000001::==--
--==::BOUNDARY::000002::==--
--==::BOUNDARY::000003::==--
--==::BOUNDARY::000004::==--
|}]
;;
let from_existing_boundary str = Sequence.append (Sequence.singleton str) default
let%expect_test _ =
Sequence.take (from_existing_boundary "BOUNDARY") 5 |> Sequence.iter ~f:print_endline;
[%expect
{|
BOUNDARY
--==::BOUNDARY::000000::==--
--==::BOUNDARY::000001::==--
--==::BOUNDARY::000002::==--
--==::BOUNDARY::000003::==--
|}]
;;
let find_nonflicting t parts =
Sequence.find_exn t ~f:(fun t ->
List.for_all parts ~f:(fun part ->
not (String_monoid.is_substring part ~substring:t)))
;;
let%expect_test _ =
find_nonflicting
(from_existing_boundary "BOUNDARY")
[ String_monoid.of_string "foobar" ]
|> print_endline;
[%expect {| BOUNDARY |}]
;;
let%expect_test _ =
find_nonflicting
(from_existing_boundary "BOUNDARY")
[ String_monoid.of_string "--BOUNDARY--" ]
|> print_endline;
[%expect {| --==::BOUNDARY::000000::==-- |}]
;;
let%expect_test _ =
find_nonflicting
(from_existing_boundary "BOUNDARY")
[ String_monoid.of_string "...BOUNDARY...--==::BOUNDARY::000000::==--..." ]
|> print_endline;
[%expect {| --==::BOUNDARY::000001::==-- |}]
;;
end
let generate_non_conflicting_boundary ?prologue ~parts ?epilogue t =
Generator.find_nonflicting
t
((Option.to_list prologue |> List.map ~f:Bigstring_shared.to_string_monoid)
@ parts
@ (Option.to_list epilogue |> List.map ~f:Bigstring_shared.to_string_monoid))
;;
let join_without_checking_for_conflicts ?prologue ~parts ?epilogue t =
if List.is_empty parts
then (
match prologue, epilogue with
| Some prologue, Some epilogue ->
String_monoid.plus
(Bigstring_shared.to_string_monoid prologue)
(Bigstring_shared.to_string_monoid epilogue)
| Some content, None | None, Some content -> Bigstring_shared.to_string_monoid content
| None, None -> String_monoid.of_string "\n")
else (
let boundary_open_first = t |> Open_first.to_string_monoid in
let boundary_open = t |> Open.to_string_monoid in
let boundary_close = t |> Close.to_string_monoid in
let first_boundary =
if Option.is_some prologue then boundary_open else boundary_open_first
in
let prologue =
Option.value_map
prologue
~f:Bigstring_shared.to_string_monoid
~default:String_monoid.empty
in
let inner_boundary = boundary_open in
let last_boundary = boundary_close in
let epilogue =
Option.value_map
epilogue
~f:Bigstring_shared.to_string_monoid
~default:String_monoid.empty
in
String_monoid.concat
[ prologue
; first_boundary
; String_monoid.concat ~sep:inner_boundary parts
; last_boundary
; epilogue
])
;;