///// Email content encoding utilities for MIME messages. ///// This module provides functions for encoding email addresses and strings ///// using various MIME content transfer encodings such as Base64, Quoted-Printable, ///// and others defined in RFC standards. import gleam/int import gleam/list import gleam/pair import gleam/result import internal/encoder/base64 as b64 import internal/encoder/bit import internal/encoder/encoding.{ type Encoding, Base64, Bit, PercentEncoding, QuotedPrintable, Text, } import internal/encoder/idna import internal/encoder/percent_encoding as pe import internal/encoder/quoted_printable as qp import internal/encoder/text /// Encodes an email address using IDNA (Internationalized Domain Names in Applications). pub fn encode_email_address( email address: String, with mode: encoding.EncodingMode, ) -> Result(String, encoding.EncoderError) { idna.encode_email_address(address, mode) } /// Encodes a string using the specified content transfer encoding. /// /// This function delegates to the appropriate encoder based on the encoding type. pub fn encode_string( encode string: String, using encoding: Encoding, with mode: encoding.EncodingMode, start position: Int, preferred_size preferred_size: Int, maximum_size maximum_size: Int, ) -> Result(List(String), encoding.EncoderError) { case encoding { Base64 -> b64.encode_string(string, position, preferred_size) Bit(bits) -> bit.encode_string(string, bits, mode, maximum_size) PercentEncoding -> pe.encode_string(string, position, preferred_size) QuotedPrintable(rfc) -> qp.encode_string(string, rfc, position, preferred_size) Text(field_type) -> text.encode_string( string, field_type, mode, position, preferred_size, maximum_size, ) } } /// Determines the optimal order of encodings to try for encoding a string within a size limit. /// /// Estimates the encoded size for each encoding and returns them sorted by size /// from smallest to largest, filtering out encodings that would exceed the maximum size. /// This is useful for content negotiation to find the most efficient encoding. pub fn determine_encoding_order( order encodings: List(Encoding), encode string: String, with mode: encoding.EncodingMode, maximum_size maximum_size: Int, ) -> List(Encoding) { encodings |> list.map(fn(encoding) { encoding |> estimate_encoded_size(mode, string, maximum_size) |> result.map(fn(size) { #(encoding, size) }) }) |> result.values() |> list.sort(fn(a, b) { int.compare(pair.second(a), pair.second(b)) }) |> list.map(pair.first) } /// Returns the canonical string name for an encoding type. pub fn name(encoding: Encoding) -> String { case encoding { Base64 -> "base64" Bit(bits) -> int.to_string(bits) <> "bit" PercentEncoding -> "percent-encoding" QuotedPrintable(_) -> "quoted-printable" Text(_) -> "quoted-string" } } fn estimate_encoded_size( encoding: Encoding, mode: encoding.EncodingMode, value: String, maximum_size: Int, ) -> Result(Int, encoding.EncoderError) { case encoding { Base64 -> b64.estimate_encoded_size(value) Bit(bits) -> bit.estimate_encoded_size(value, bits, mode, maximum_size) PercentEncoding -> pe.estimate_encoded_size(value, maximum_size) QuotedPrintable(rfc) -> qp.estimate_encoded_size(value, rfc) Text(field_type) -> text.estimate_encoded_size(value, field_type, mode, maximum_size) } }