~/bend-docscommunity

mime.bend checks

raw source on the hub · import 0xfd64aefef43747d3eaeea50c10a112e5/mime.bend as Mime

MIME and message format: UTF-8 bytes, base64 (RFC 4648), the Date header (RFC 5322 3.3), encoded words for non-ASCII subjects (RFC 2047) and a transfer encoding that keeps every line legal (RFC 2045, RFC 5322 2.1.1). The whole message comes out 7-bit ASCII, so it needs no 8BITMIME or SMTPUTF8 from the server.

1 import
import Base

Types

type Words source · line 197 · raw

Data

Encoded words built so far, the bytes of the current one and its size.

type Body source · line 272 · raw

Data

The body's transfer encoding and its text: 7bit as written, else base64 of its UTF-8 (CRLF line breaks first, RFC 2045 6.8).

type Part source · line 346 · raw

Data

An attachment: its file name, content type and bytes.

Definitions

def Word.pick source · line 8 · raw

@c:Bool -> @a:U32 -> @b:U32 -> U32

def Utf8.cont source · line 18 · raw

@+c:U32 -> @n:Nat -> U32

def Utf8.size source · line 22 · raw

@+c:U32 -> Nat

How many bytes a code point takes.

def Utf8.put.k source · line 27 · raw

@k:Nat -> @+c:U32 -> @rest:List<&2, U32> -> List<&2, U32>

def Utf8.put source · line 41 · raw

@+c:U32 -> @rest:List<&2, U32> -> List<&2, U32>

One code point's bytes, put in front of rest.

def Utf8.bytes source · line 44 · raw

@s:String -> List<&2, U32>

def B64.ch source · line 55 · raw

@+i:U32 -> Char

The base64 digit for 0..63.

def B64.at source · line 61 · raw

@+x:U32 -> @n:Nat -> Char

def B64.quad source · line 65 · raw

@+x:U32 -> @pad:Nat -> @rest:String -> String

A group of 4 digits, the last pad of them "=".

def B64.word source · line 76 · raw

@+a:U32 -> @+b:U32 -> @+c:U32 -> U32

def B64.onto source · line 79 · raw

@acc:String -> @tail:String -> String

def B64.quad.rev source · line 87 · raw

@+x:U32 -> @pad:Nat -> @acc:String -> String

The 4 digits of a group, reversed, on acc; pad of them "=".

def B64.brk source · line 98 · raw

@full:Bool -> @acc:String -> String

A CRLF (reversed) before the group when wrap groups fill the line.

def B64.go source · line 108 · raw

@bs:List<&2, U32> -> @+k:U32 -> @+wrap:U32 -> @acc:String -> @tail:String -> String

The digits of bs, in front of tail; k groups are on the current line, and a line takes wrap groups (0: one line). acc holds the digits so far, reversed: one tail call per group, then one pass onto the tail.

def B64.encode source · line 124 · raw

@bs:List<&2, U32> -> String

def B64.lines source · line 129 · raw

@bs:List<&2, U32> -> @tail:String -> String

Base64 in lines of 76 digits (19 groups), as MIME bodies need, then tail.

def B64.text source · line 132 · raw

@s:String -> String

def Date.pad source · line 138 · raw

@+n:U32 -> String

def Date.day source · line 141 · raw

@+w:U32 -> String

def Date.month source · line 149 · raw

@+m:U32 -> String

def Date.civil source · line 164 · raw

@+days:U32 -> String

Year, month and day of a day count since 1970-01-01 (H. Hinnant's civil_from_days, for days >= 0), as "DD Mon YYYY".

def Date.fmt source · line 177 · raw

@+t:U32 -> String

The Date header's value for t seconds since 1970, in UTC.

def Mime.printable source · line 189 · raw

@s:String -> Bool

Whether every char is printable ASCII (space to tilde).

def Mime.word source · line 200 · raw

@cur:List<&2, U32> -> String

def Mime.add.at source · line 204 · raw

@full:Bool -> @out:String -> @cur:List<&2, U32> -> @+n:U32 -> @bc:List<&2, U32> -> @+k:U32 -> Words

39 bytes make 52 digits: "Subject: " plus one word is 73 columns.

def Mime.add source · line 212 · raw

@w:Words -> @+c:U32 -> Words

def Mime.words source · line 217 · raw

@s:String -> @w:Words -> String

def Mime.encoded source · line 226 · raw

@s:String -> String

s as RFC 2047 encoded words of at most 39 bytes each, folded.

def Mime.subject.at source · line 229 · raw

@plain:Bool -> @s:String -> String

def Mime.subject source · line 238 · raw

@+s:String -> String

A Subject value: as is when it is printable ASCII that fits one line and cannot read as an encoded word, else encoded words, folded.

def Mime.crlf source · line 246 · raw

@s:String -> String

Line breaks as CRLF, bare CRs dropped.

def Mime.seven source · line 259 · raw

@s:String -> @+col:U32 -> Bool

Whether the body can go as 7bit: ASCII without NUL, and no line past 998 chars (RFC 5322 2.1.1); col counts the current line.

def Mime.body.at source · line 275 · raw

@seven:Bool -> @s:String -> Body

def Mime.body source · line 283 · raw

@+s:String -> Body

Text goes 7bit only if it cannot hold a part boundary (Mime.boundary).

def B64.val source · line 290 · raw

@+c:U32 -> U32

A base64 digit's value, or 64 for anything else.

def B64.keep source · line 296 · raw

@ok:Bool -> @v:U32 -> @rest:List<&2, U32> -> List<&2, U32>

def B64.vals source · line 303 · raw

@s:String -> List<&2, U32>

def B64.byte source · line 311 · raw

@+x:U32 -> @n:Nat -> U32

def B64.group source · line 314 · raw

@+a:U32 -> @+b:U32 -> @+c:U32 -> @+d:U32 -> U32

def B64.bytes source · line 317 · raw

@vs:List<&2, U32> -> List<&2, U32>

def B64.decode source · line 331 · raw

@s:String -> List<&2, U32>

The bytes of base64 text; padding and anything off the alphabet skipped.

def B64.ascii source · line 335 · raw

@bs:List<&2, U32> -> String

Bytes as text: ASCII as is, any other byte as "?" (for error messages).

def Mime.ext.is source · line 349 · raw

@+name:String -> @e:String -> Bool

def Mime.ctype source · line 354 · raw

@+n:String -> String

A content type from the file name's extension (application/octet-stream when unknown). message/rfc822 is never used: it may not be base64.

def Mime.attr source · line 382 · raw

@+c:U32 -> Bool

attr-char (RFC 2231 7): ALPHA DIGIT !#$&+-.^_`|~.

def Mime.hex source · line 389 · raw

@+d:U32 -> Char

def Mime.pct.at source · line 392 · raw

@keep:Bool -> @+b:U32 -> @rest:String -> String

def Mime.pct source · line 400 · raw

@bs:List<&2, U32> -> String

Bytes percent-encoded outside attr-char (RFC 2231 4).

def Mime.safe source · line 407 · raw

@s:String -> Bool

def Mime.param.at source · line 415 · raw

@safe:Bool -> @+key:String -> @+n:String -> String

def Mime.param source · line 425 · raw

@+key:String -> @+n:String -> String

A file name parameter: quoted when the name is safe ASCII, else only in RFC 2231's extended form (as Python's email package writes it: a second, ASCII form would be read first by some parsers).

def Mime.leaf.with source · line 432 · raw

@ctype:String -> @b:Body -> String

A text leaf: its headers, a blank line, its body.

def Mime.leaf source · line 437 · raw

@ctype:String -> @+s:String -> String

def Mime.attach source · line 441 · raw

@p:Part -> @rest:String -> String

An attachment leaf, always base64, in front of rest.

def Mime.parts source · line 447 · raw

@+b:String -> @ps:List<&2, String> -> String

def Mime.preamble source · line 454 · raw

String

def Mime.multi source · line 459 · raw

@sub:String -> @+b:String -> @ps:List<&2, String> -> String

A multipart entity (RFC 2046 5.1): a boundary that no part can hold, then each part after a delimiter line, then the close delimiter.

def Mime.attaches source · line 464 · raw

@fs:List<&2, Part> -> @+b:String -> String

The files as parts, each after its delimiter, then the close one.

def Mime.alt.at source · line 471 · raw

@none:Bool -> @text:String -> @html:String -> @+b:String -> String

def Mime.mixed.at source · line 479 · raw

@none:Bool -> @alt:String -> @files:List<&2, Part> -> @+b:String -> String

def Mime.entity source · line 493 · raw

@text:String -> @+html:String -> @+files:List<&2, Part> -> @+b:String -> String

The message's entity: text alone; text and HTML as alternatives (plain first, RFC 2046 5.1.4); either one with files as mixed. b is the boundary stem: base64 and the 7bit texts (Mime.body) cannot hold it.

def Mime.boundary source · line 499 · raw

@+hex:String -> String

A boundary stem from random words: "=_bend_" and hex.