My agentic slop goes here. Not intended for anyone else!
1(** Email body part and body value representation.
2
3 This module provides types and operations for email body parts and decoded
4 body values as defined in RFC 8621 Section 4.1.4. Body parts represent the
5 MIME structure of email messages, while body values contain decoded text content.
6
7 Body parts can be either leaf parts containing actual content or multipart
8 containers holding sub-parts. They include information about MIME type, encoding,
9 disposition, size, and other RFC 2045-2047 MIME attributes.
10
11 @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4 - Email Body Structure
12*)
13
14
15(** Email body part representation.
16
17 Represents a single part within an email's MIME structure as specified in
18 RFC 8621 Section 4.1.4. Each body part can be either a leaf part containing
19 actual content or a multipart container holding sub-parts.
20*)
21type t
22
23(** JSON serialization interface *)
24include Jmap_sigs.JSONABLE with type t := t
25
26(** Pretty-printing interface *)
27include Jmap_sigs.PRINTABLE with type t := t
28
29(** Get the part ID for referencing this specific part.
30 @param t The body part
31 @return Part identifier, or None for multipart container types *)
32val id : t -> string option
33
34(** Get the blob ID for downloading the part content.
35 @param t The body part
36 @return Blob identifier for content access, or None for multipart types *)
37val blob_id : t -> Jmap.Id.t option
38
39(** Get the size of the part in bytes.
40 @param t The body part
41 @return Size in bytes of the decoded content *)
42val size : t -> Jmap.UInt.t
43
44(** Get the list of MIME headers for this part.
45 @param t The body part
46 @return List of header fields specific to this body part *)
47val headers : t -> Header.t list
48
49(** Get the filename parameter from Content-Disposition or Content-Type.
50 @param t The body part
51 @return Filename if present, None otherwise *)
52val name : t -> string option
53
54(** Get the MIME content type.
55 @param t The body part
56 @return MIME type (e.g., "text/plain", "image/jpeg") *)
57val mime_type : t -> string
58
59(** Get the character set parameter.
60 @param t The body part
61 @return Character encoding (e.g., "utf-8", "iso-8859-1"), None if not specified *)
62val charset : t -> string option
63
64(** Get the Content-Disposition header value.
65 @param t The body part
66 @return Disposition type (e.g., "attachment", "inline"), None if not specified *)
67val disposition : t -> string option
68
69(** Get the Content-Disposition parameters.
70 @param t The body part
71 @return Map of disposition parameters (e.g., filename), None if not present *)
72val disposition_params : t -> (string, string) Hashtbl.t option
73
74(** Get the boundary parameter for multipart types.
75 @param t The body part
76 @return Boundary string for multipart content, None otherwise *)
77val boundary : t -> string option
78
79(** Get the Content-Transfer-Encoding header value.
80 @param t The body part
81 @return Transfer encoding method (e.g., "base64", "quoted-printable"), None if not specified *)
82val content_transfer_encoding : t -> string option
83
84(** Get the Content-ID header value for referencing within HTML content.
85 @param t The body part
86 @return Content identifier for inline references, None if not specified *)
87val cid : t -> string option
88
89(** Get the Content-Language header values.
90 @param t The body part
91 @return List of language codes (e.g., ["en"; "fr"]), None if not specified *)
92val language : t -> string list option
93
94(** Get the Content-Location header value.
95 @param t The body part
96 @return URI reference for content location, None if not specified *)
97val location : t -> string option
98
99(** Get nested parts for multipart content types.
100 @param t The body part
101 @return List of sub-parts for multipart types, None for leaf parts *)
102val sub_parts : t -> t list option
103
104(** Get additional headers requested via header properties.
105 @param t The body part
106 @return Map of header names to their JSON values for extended header access *)
107val other_headers : t -> (string, Yojson.Safe.t) Hashtbl.t
108
109(** Create a new body part object.
110
111 Creates a body part with validation of required fields and proper MIME structure.
112 Either Jmap.Id.t+blob_id (for leaf parts) or sub_parts (for multipart) should be provided,
113 but not both.
114
115 @param Jmap.Id.t Optional part identifier for leaf parts
116 @param blob_id Optional blob ID for content access
117 @param size Size in bytes of decoded content
118 @param headers List of MIME headers for this part
119 @param name Optional filename parameter
120 @param mime_type MIME content type
121 @param charset Optional character encoding
122 @param disposition Optional Content-Disposition value
123 @param cid Optional Content-ID for inline references
124 @param language Optional Content-Language codes
125 @param location Optional Content-Location URI
126 @param sub_parts Optional list of nested parts (for multipart types)
127 @param other_headers Optional additional headers map
128 @return Result containing new body part or validation error *)
129val create :
130 ?id:string ->
131 ?blob_id:Jmap.Id.t ->
132 size:Jmap.UInt.t ->
133 headers:Header.t list ->
134 ?name:string ->
135 mime_type:string ->
136 ?charset:string ->
137 ?disposition:string ->
138 ?disposition_params:(string, string) Hashtbl.t ->
139 ?cid:string ->
140 ?language:string list ->
141 ?location:string ->
142 ?sub_parts:t list ->
143 ?boundary:string ->
144 ?content_transfer_encoding:string ->
145 ?other_headers:(string, Yojson.Safe.t) Hashtbl.t ->
146 unit -> (t, string) result
147
148(** Create a new body part object without validation.
149
150 For use when body parts are known to be valid or come from trusted sources
151 like server responses.
152
153 @param Jmap.Id.t Optional part identifier for leaf parts
154 @param blob_id Optional blob ID for content access
155 @param size Size in bytes of decoded content
156 @param headers List of MIME headers for this part
157 @param name Optional filename parameter
158 @param mime_type MIME content type
159 @param charset Optional character encoding
160 @param disposition Optional Content-Disposition value
161 @param cid Optional Content-ID for inline references
162 @param language Optional Content-Language codes
163 @param location Optional Content-Location URI
164 @param sub_parts Optional list of nested parts (for multipart types)
165 @param other_headers Optional additional headers map
166 @return New body part object *)
167val create_unsafe :
168 ?id:string ->
169 ?blob_id:Jmap.Id.t ->
170 size:Jmap.UInt.t ->
171 headers:Header.t list ->
172 ?name:string ->
173 mime_type:string ->
174 ?charset:string ->
175 ?disposition:string ->
176 ?disposition_params:(string, string) Hashtbl.t ->
177 ?cid:string ->
178 ?language:string list ->
179 ?location:string ->
180 ?sub_parts:t list ->
181 ?boundary:string ->
182 ?content_transfer_encoding:string ->
183 ?other_headers:(string, Yojson.Safe.t) Hashtbl.t ->
184 unit -> t
185
186(** Check if body part is a multipart container.
187 @param t The body part
188 @return true if this is a multipart container with sub-parts *)
189val is_multipart : t -> bool
190
191(** Check if body part is a leaf part with content.
192 @param t The body part
193 @return true if this is a leaf part with blob content *)
194val is_leaf : t -> bool
195
196(** Check if body part is an attachment.
197
198 Determines if the part should be treated as an attachment based on
199 Content-Disposition and other heuristics.
200
201 @param t The body part
202 @return true if this part should be treated as an attachment *)
203val is_attachment : t -> bool
204
205(** Check if body part is inline content.
206
207 Determines if the part should be displayed inline based on
208 Content-Disposition and MIME type.
209
210 @param t The body part
211 @return true if this part should be displayed inline *)
212val is_inline : t -> bool
213
214(** Get all leaf parts (non-multipart parts) from a body part tree.
215
216 Recursively traverses multipart structures to find all leaf parts
217 containing actual content.
218
219 @param t The body part (may be multipart or leaf)
220 @return List of all leaf parts in the structure *)
221val get_leaf_parts : t -> t list
222
223(** Find body parts by MIME type.
224
225 Searches the body part tree for parts matching the specified MIME type.
226 Supports exact matching and wildcard patterns (e.g., "text/*").
227
228 @param t The body part tree to search
229 @param mime_type MIME type to match (supports wildcards)
230 @return List of matching body parts *)
231val find_by_mime_type : t -> string -> t list
232
233(** Generate a unique part ID for a body part at given depth and position.
234 @param depth The nesting depth (0 for top level)
235 @param position The position within the current level
236 @return Generated part ID string *)
237val generate_part_id : int -> int -> string
238
239(** Validate part ID format according to MIME structure.
240 @param part_id The part ID to validate
241 @return true if the part ID has valid format *)
242val is_valid_part_id : string -> bool
243
244(** Get text body parts for textBody property as per RFC 8621 algorithm.
245 @param t The body structure to flatten
246 @return List of parts to display as text body *)
247val get_text_body : t -> t list
248
249(** Get HTML body parts for htmlBody property as per RFC 8621 algorithm.
250 @param t The body structure to flatten
251 @return List of parts to display as HTML body *)
252val get_html_body : t -> t list
253
254(** Get attachment parts for attachments property as per RFC 8621 algorithm.
255 @param t The body structure to flatten
256 @return List of parts to treat as attachments *)
257val get_attachments : t -> t list
258
259(** Extract MIME parameters from Content-Type header in headers list.
260 @param headers List of headers to search
261 @return Content-Type value and parameter list *)
262val extract_mime_params : Header.t list -> string option * (string * string) list
263
264(** Extract Content-Disposition parameters from headers list.
265 @param headers List of headers to search
266 @return Disposition type and parameter list *)
267val extract_disposition_params : Header.t list -> string option * (string * string) list
268
269
270(** Decoded email body content.
271
272 Represents the decoded text content of a body part as specified in RFC 8621
273 Section 4.1.4. This provides access to the actual text content after MIME
274 decoding, along with metadata about potential encoding issues or truncation.
275*)
276module Value : sig
277 (** Decoded body value type *)
278 type t
279
280 (** Get the decoded text content.
281 @param t The body value
282 @return The decoded text content of the body part *)
283 val value : t -> string
284
285 (** Check if there was an encoding problem during decoding.
286 @param t The body value
287 @return true if encoding issues were encountered during decoding *)
288 val has_encoding_problem : t -> bool
289
290 (** Check if the content was truncated by the server.
291 @param t The body value
292 @return true if the content was truncated to fit size limits *)
293 val is_truncated : t -> bool
294
295 (** Create a new body value object.
296 @param value The decoded text content
297 @param encoding_problem Whether encoding problems were encountered (default: false)
298 @param truncated Whether the content was truncated (default: false)
299 @return New body value object *)
300 val create :
301 value:string ->
302 ?encoding_problem:bool ->
303 ?truncated:bool ->
304 unit -> t
305
306 (** Create body value from raw MIME part content with full decoding.
307
308 Applies Content-Transfer-Encoding decoding and character set handling
309 as specified in RFC 8621.
310
311 @param part_content Raw MIME part content
312 @param content_type Content-Type header value for charset extraction
313 @param content_transfer_encoding Transfer encoding method
314 @param max_bytes Maximum bytes to include (0 for no limit)
315 @return Body value with decoded content and encoding problem flags *)
316 val from_mime_part :
317 part_content:string ->
318 content_type:string option ->
319 content_transfer_encoding:string option ->
320 max_bytes:int ->
321 unit -> t
322
323 (** Check if body value contains displayable text content.
324 @param t The body value
325 @return true if content is non-empty after trimming whitespace *)
326 val is_text_content : t -> bool
327
328 (** Get content length in bytes.
329 @param t The body value
330 @return Number of bytes in the decoded content *)
331 val content_length : t -> int
332
333 (** Get content preview (first N characters).
334 @param t The body value
335 @param max_chars Maximum characters to include in preview
336 @return Content preview with ellipsis if truncated *)
337 val preview : t -> max_chars:int -> string
338
339 (** Convert body value to JSON representation.
340
341 Produces JSON object with "value" string field and optional boolean fields
342 for "isEncodingProblem" and "isTruncated" as specified in JMAP.
343
344 @param t The body value to convert
345 @return JSON object with body value fields *)
346 val to_json : t -> Yojson.Safe.t
347
348 (** Parse body value from JSON representation.
349
350 Parses body value from JSON object as received in Email/get responses
351 in the "bodyValues" property.
352
353 @param json JSON object representing a body value
354 @return Result containing parsed body value or parse error *)
355 val of_json : Yojson.Safe.t -> (t, string) result
356end