# Halon Scripting Language
Version: 6.12-stable
[Halon](http://halon.io/) is a fast, flexible and powerful email infrastructure software;
designed for demanding uses such as large-scale email services.
The Halon scripting language is a domain-specific language ([DSL](http://en.wikipedia.org/wiki/Domain-specific_language)).
It enables configuration of the behavior of email processing and delivery in a scriptable fashion.
This script language documentation references many other resources, such as:
* [Manual](https://docs.halon.io/manual/)
* [Script library](https://docs.halon.io/go/scriptlibrary)
* [Knowledge base](https://docs.halon.io/go/knowledgebase)
## Table of contents
* [About](#about)
* [Design](#about:design)
* [Introduction](#introduction)
* [Variables](#introduction:variables)
* [Functions](#introduction:functions)
* [Comments](#introduction:comments)
* [Data types and literals](#literals)
* [Boolean](#literals:boolean)
* [Number](#literals:number)
* [Hexadecimal](#literals:hexadecimal)
* [Octal](#literals:octal)
* [Binary](#literals:binary)
* [String](#literals:string)
* [Double-quoted string](#literals:double-quoted-string)
* [Raw string](#literals:raw-string)
* [Regex](#literals:regex)
* [Pattern modifiers](#literals:pattern-modifiers)
* [Array](#literals:arraytype)
* [Function](#literals:function)
* [Anonymous functions](#literals:anonymous-functions)
* [Named function pointers](#literals:named-function-pointers)
* [Object](#literals:object)
* [`Iterator`](#literals:Iterator)
* [`Iterator.next()`](#literals:Iterator.next)
* [None](#literals:id12)
* [Operators](#operators)
* [Assignment](#operators:assignment)
* [Augmented assignment](#operators:augmented-assignment)
* [Nullish Coalescing assignment](#operators:nullish-coalescing-assignment)
* [Destructuring assignment](#operators:destructuring-assignment)
* [Slice assignment](#operators:slice-assignment)
* [Arithmetic](#operators:arithmetic)
* [String](#operators:string)
* [Concatenation](#operators:concatenation)
* [Format](#operators:format)
* [Repeat](#operators:repeat)
* [Array](#operators:array)
* [Subscript](#operators:subscript)
* [Slicing](#operators:slicing)
* [Push and pop](#operators:push-and-pop)
* [Removing](#operators:removing)
* [Repeat](#operators:id3)
* [Spread](#operators:spread)
* [Logic (boolean)](#operators:logic-boolean)
* [Short-circuit evaluation](#operators:short-circuit-evaluation)
* [Bitwise](#operators:bitwise)
* [Comparison](#operators:comparison)
* [Loose equality table](#operators:loose-equality-table)
* [Truthiness](#operators:truthiness)
* [Regular expression](#operators:regular-expression)
* [Function](#operators:function)
* [Call](#operators:call)
* [Class](#operators:class)
* [Property](#operators:property)
* [Scope resolution](#operators:scope-resolution)
* [Static](#operators:static)
* [Control structures](#structures)
* [echo](#structures:echo)
* [if](#structures:if)
* [Ternary operator](#structures:ternary-operator)
* [Null coalescing operator](#structures:null-coalescing-operator)
* [for](#structures:for)
* [break](#structures:break)
* [continue](#structures:continue)
* [foreach](#structures:foreach)
* [break](#structures:id5)
* [continue](#structures:id6)
* [while](#structures:while)
* [break](#structures:id7)
* [continue](#structures:id8)
* [forever](#structures:forever)
* [break](#structures:id9)
* [continue](#structures:id10)
* [switch](#structures:switch)
* [match](#structures:match)
* [include](#structures:include)
* [include_once](#structures:include-once)
* [import](#structures:import)
* [variables](#structures:variables)
* [functions](#structures:functions)
* [wildcard](#structures:wildcard)
* [data](#structures:data)
* [wildcard file imports](#structures:wildcard-file-imports)
* [function](#structures:function)
* [Named functions](#structures:named-functions)
* [Function scope](#structures:function-scope)
* [Anonymous functions](#structures:anonymous-functions)
* [Closure functions](#structures:closure-functions)
* [return](#structures:return)
* [Default argument](#structures:default-argument)
* [Variadic function](#structures:variadic-function)
* [global](#structures:global)
* [Function calling](#structures:function-calling)
* [Argument unpacking](#structures:argument-unpacking)
* [builtin](#structures:builtin)
* [`function*`](#structures:id13)
* [generator object](#structures:generator-object)
* [yield](#structures:yield)
* [Exceptions](#structures:exceptions)
* [try](#structures:try)
* [catch](#structures:catch)
* [throw](#structures:throw)
* [class](#structures:class)
* [constructor](#structures:constructor)
* [Instance](#structures:instance)
* [$this](#structures:this)
* [variables](#structures:id18)
* [functions](#structures:id19)
* [Static](#structures:static)
* [variables](#structures:id20)
* [functions](#structures:id21)
* [Visibility](#structures:visibility)
* [private](#structures:private)
* [Permissions](#structures:permissions)
* [readonly](#structures:readonly)
* [cache](#structures:cache)
* [barrier](#structures:barrier)
* [Standard library](#functions)
* [Array](#functions:array)
* [`array_combine()`](#functions:array_combine)
* [`array_every()`](#functions:array_every)
* [`array_filter()`](#functions:array_filter)
* [`array_find()`](#functions:array_find)
* [`array_includes()`](#functions:array_includes)
* [`array_join()`](#functions:array_join)
* [`array_keys()`](#functions:array_keys)
* [`array_map()`](#functions:array_map)
* [`array_random()`](#functions:array_random)
* [`array_range()`](#functions:array_range)
* [`array_reduce()`](#functions:array_reduce)
* [`array_reverse()`](#functions:array_reverse)
* [`array_sort()`](#functions:array_sort)
* [`array_shuffle()`](#functions:array_shuffle)
* [`array_unique()`](#functions:array_unique)
* [Cryptographic](#functions:cryptographic)
* [`aes_decrypt()`](#functions:aes_decrypt)
* [`aes_encrypt()`](#functions:aes_encrypt)
* [`hmac_md5()`](#functions:hmac_md5)
* [`hmac_sha1()`](#functions:hmac_sha1)
* [`hmac_sha2()`](#functions:hmac_sha2)
* [`md5()`](#functions:md5)
* [`sha1()`](#functions:sha1)
* [`sha2()`](#functions:sha2)
* [`hash()`](#functions:hash)
* [`pkcs11_load()`](#functions:pkcs11_load)
* [`rsa_privatekey()`](#functions:rsa_privatekey)
* [`is_rsa_privatekey()`](#functions:is_rsa_privatekey)
* [`rsa_sign()`](#functions:rsa_sign)
* [`rsa_verify()`](#functions:rsa_verify)
* [`ec_privatekey()`](#functions:ec_privatekey)
* [`is_ec_privatekey()`](#functions:is_ec_privatekey)
* [`ed25519_privatekey()`](#functions:ed25519_privatekey)
* [`is_ed25519_privatekey()`](#functions:is_ed25519_privatekey)
* [`ed25519_sign()`](#functions:ed25519_sign)
* [`ed25519_verify()`](#functions:ed25519_verify)
* [`pkcs7_sign()`](#functions:pkcs7_sign)
* [`cms_sign()`](#functions:cms_sign)
* [`cms_verify()`](#functions:cms_verify)
* [`cms_encrypt()`](#functions:cms_encrypt)
* [`cms_decrypt()`](#functions:cms_decrypt)
* [`cms_type()`](#functions:cms_type)
* [`cms_recipients()`](#functions:cms_recipients)
* [`random_bytes()`](#functions:random_bytes)
* [`random_number()`](#functions:random_number)
* [`crypt()`](#functions:crypt)
* [Data types](#functions:data-types)
* [`length()`](#functions:length)
* [`array()`](#functions:id0)
* [`boolean()`](#functions:boolean)
* [`number()`](#functions:number)
* [`string()`](#functions:string)
* [`is_array()`](#functions:is_array)
* [`is_boolean()`](#functions:is_boolean)
* [`is_function()`](#functions:is_function)
* [`is_number()`](#functions:is_number)
* [`is_object()`](#functions:is_object)
* [`is_string()`](#functions:is_string)
* [`isset()`](#functions:isset)
* [`unset()`](#functions:unset)
* [`Map`](#functions:Map)
* [`Map.constructor()`](#functions:Map.constructor)
* [`Map.set()`](#functions:Map.set)
* [`Map.has()`](#functions:Map.has)
* [`Map.get()`](#functions:Map.get)
* [`Map.delete()`](#functions:Map.delete)
* [`Map.merge()`](#functions:Map.merge)
* [`Map.size()`](#functions:Map.size)
* [`Map.clear()`](#functions:Map.clear)
* [`Map.toArray()`](#functions:Map.toArray)
* [`Map.entries()`](#functions:Map.entries)
* [`Set`](#functions:Set)
* [`Set.constructor()`](#functions:Set.constructor)
* [`Set.add()`](#functions:Set.add)
* [`Set.has()`](#functions:Set.has)
* [`Set.delete()`](#functions:Set.delete)
* [`Set.size()`](#functions:Set.size)
* [`Set.clear()`](#functions:Set.clear)
* [`Set.toArray()`](#functions:Set.toArray)
* [`Set.values()`](#functions:Set.values)
* [Date and time](#functions:date-and-time)
* [`executiontime()`](#functions:executiontime)
* [`sleep()`](#functions:sleep)
* [`strftime()`](#functions:strftime)
* [`strptime()`](#functions:strptime)
* [`strpduration()`](#functions:strpduration)
* [`time()`](#functions:time)
* [`timelocal()`](#functions:timelocal)
* [`uptime()`](#functions:uptime)
* [DNS](#functions:dns)
* [`dns_query()`](#functions:dns_query)
* [`domain_includes()`](#functions:domain_includes)
* [`domain_publicsuffix()`](#functions:domain_publicsuffix)
* [`idna_encode()`](#functions:idna_encode)
* [`idna_decode()`](#functions:idna_decode)
* [Encodings and JSON](#functions:encodings-and-json)
* [`base32_encode()`](#functions:base32_encode)
* [`base32_decode()`](#functions:base32_decode)
* [`base64_encode()`](#functions:base64_encode)
* [`base64_decode()`](#functions:base64_decode)
* [`base64url_encode()`](#functions:base64url_encode)
* [`base64url_decode()`](#functions:base64url_decode)
* [`zlib_compress()`](#functions:zlib_compress)
* [`zlib_uncompress()`](#functions:zlib_uncompress)
* [`csv_encode()`](#functions:csv_encode)
* [`csv_decode()`](#functions:csv_decode)
* [`json_encode()`](#functions:json_encode)
* [`json_decode()`](#functions:json_decode)
* [`url_encode()`](#functions:url_encode)
* [`url_decode()`](#functions:url_decode)
* [`mimeword_encode()`](#functions:mimeword_encode)
* [`mimeword_decode()`](#functions:mimeword_decode)
* [`yaml_decode()`](#functions:yaml_decode)
* [`pack()`](#functions:pack)
* [`unpack()`](#functions:unpack)
* [`Iconv`](#functions:Iconv)
* [`Iconv.constructor()`](#functions:Iconv.constructor)
* [`Iconv.close()`](#functions:Iconv.close)
* [`Iconv.convert()`](#functions:Iconv.convert)
* [`Iconv.errno()`](#functions:Iconv.errno)
* [File and HTTP](#functions:file-and-http)
* [`File`](#functions:File)
* [`File.constructor()`](#functions:File.constructor)
* [`File.close()`](#functions:File.close)
* [`File.read()`](#functions:File.read)
* [`File.readline()`](#functions:File.readline)
* [`File.seek()`](#functions:File.seek)
* [`File.tell()`](#functions:File.tell)
* [`File.getPath()`](#functions:File.getPath)
* [`File.toFFIValue()`](#functions:File.toFFIValue)
* [`File.String()`](#functions:File.String)
* [`http()`](#functions:http)
* [`url_parse()`](#functions:url_parse)
* [Mail](#functions:mail)
* [`envelope_address_parse()`](#functions:envelope_address_parse)
* [`envelope_localpart_escape()`](#functions:envelope_localpart_escape)
* [`header_addresslist_compile()`](#functions:header_addresslist_compile)
* [`header_addresslist_extract()`](#functions:header_addresslist_extract)
* [`header_dkim_decode()`](#functions:header_dkim_decode)
* [`xtext_encode()`](#functions:xtext_encode)
* [`xtext_decode()`](#functions:xtext_decode)
* [`spf_query()`](#functions:spf_query)
* [Mathematical](#functions:mathematical)
* [`abs()`](#functions:abs)
* [`ceil()`](#functions:ceil)
* [`floor()`](#functions:floor)
* [`log()`](#functions:log)
* [`pow()`](#functions:pow)
* [`round()`](#functions:round)
* [`sqrt()`](#functions:sqrt)
* [MIME](#functions:mime)
* [`MIME`](#functions:MIME)
* [`MIME.constructor()`](#functions:MIME.constructor)
* [`MIME.addHeader()`](#functions:MIME.addHeader)
* [`MIME.appendPart()`](#functions:MIME.appendPart)
* [`MIME.setBody()`](#functions:MIME.setBody)
* [`MIME.setType()`](#functions:MIME.setType)
* [`MIME.setBoundary()`](#functions:MIME.setBoundary)
* [`MIME.setDate()`](#functions:MIME.setDate)
* [`MIME.setFileName()`](#functions:MIME.setFileName)
* [`MIME.setDisposition()`](#functions:MIME.setDisposition)
* [`MIME.signDKIM()`](#functions:MIME.signDKIM)
* [`MIME.toString()`](#functions:MIME.toString)
* [`MIME.toFile()`](#functions:MIME.toFile)
* [`MIME.setContext()`](#functions:MIME.setContext)
* [`MIME.queue()`](#functions:MIME.queue)
* [`MIME.send()`](#functions:MIME.send)
* [`MailMessage`](#functions:_CPPv411MailMessage)
* [`MailMessage.reset()`](#functions:MailMessage.reset)
* [`MailMessage.snapshot()`](#functions:MailMessage.snapshot)
* [`MailMessage.restore()`](#functions:MailMessage.restore)
* [`MailMessage.modifyContent()`](#functions:MailMessage.modifyContent)
* [`MailMessage.toFile()`](#functions:MailMessage.toFile)
* [`MailMessage.toString()`](#functions:MailMessage.toString)
* [`MailMessage.signDKIM()`](#functions:MailMessage.signDKIM)
* [`MailMessage.verifyDKIM()`](#functions:MailMessage.verifyDKIM)
* [`MailMessage.setContext()`](#functions:MailMessage.setContext)
* [`MailMessage.send()`](#functions:MailMessage.send)
* [`MailMessage.queue()`](#functions:MailMessage.queue)
* [`MailMessage.String()`](#functions:MailMessage.String)
* [`MailMessage.File()`](#functions:MailMessage.File)
* [`MIMEPart`](#functions:_CPPv48MIMEPart)
* [`MIMEPart.getID()`](#functions:MIMEPart.getID)
* [`MIMEPart.getSize()`](#functions:MIMEPart.getSize)
* [`MIMEPart.getFileName()`](#functions:MIMEPart.getFileName)
* [`MIMEPart.getType()`](#functions:MIMEPart.getType)
* [`MIMEPart.getTypeParameter()`](#functions:MIMEPart.getTypeParameter)
* [`MIMEPart.getHeader()`](#functions:MIMEPart.getHeader)
* [`MIMEPart.getHeaders()`](#functions:MIMEPart.getHeaders)
* [`MIMEPart.getHeaderNames()`](#functions:MIMEPart.getHeaderNames)
* [`MIMEPart.setHeader()`](#functions:MIMEPart.setHeader)
* [`MIMEPart.addHeader()`](#functions:MIMEPart.addHeader)
* [`MIMEPart.delHeader()`](#functions:MIMEPart.delHeader)
* [`MIMEPart.renameHeader()`](#functions:MIMEPart.renameHeader)
* [`MIMEPart.setDateLater()`](#functions:MIMEPart.setDateLater)
* [`MIMEPart.remove()`](#functions:MIMEPart.remove)
* [`MIMEPart.getBody()`](#functions:MIMEPart.getBody)
* [`MIMEPart.setBody()`](#functions:MIMEPart.setBody)
* [`MIMEPart.getPreamble()`](#functions:MIMEPart.getPreamble)
* [`MIMEPart.setPreamble()`](#functions:MIMEPart.setPreamble)
* [`MIMEPart.getEpilogue()`](#functions:MIMEPart.getEpilogue)
* [`MIMEPart.setEpilogue()`](#functions:MIMEPart.setEpilogue)
* [`MIMEPart.prependPart()`](#functions:MIMEPart.prependPart)
* [`MIMEPart.appendPart()`](#functions:MIMEPart.appendPart)
* [`MIMEPart.replacePart()`](#functions:MIMEPart.replacePart)
* [`MIMEPart.findByType()`](#functions:MIMEPart.findByType)
* [`MIMEPart.findByFileName()`](#functions:MIMEPart.findByFileName)
* [`MIMEPart.getByID()`](#functions:MIMEPart.getByID)
* [`MIMEPart.getParts()`](#functions:MIMEPart.getParts)
* [`MIMEPart.getErrors()`](#functions:MIMEPart.getErrors)
* [Misc](#functions:misc)
* [`gethostname()`](#functions:gethostname)
* [`uuid()`](#functions:uuid)
* [`uuidv5()`](#functions:uuidv5)
* [`echo()`](#functions:echo)
* [`syslog()`](#functions:syslog)
* [`inet_includes()`](#functions:inet_includes)
* [`inet_ntop()`](#functions:inet_ntop)
* [`inet_pton()`](#functions:inet_pton)
* [`inet_reverse()`](#functions:inet_reverse)
* [Protocols](#functions:protocols)
* [`smtp_lookup_rcpt()`](#functions:smtp_lookup_rcpt)
* [`smtp_lookup_auth()`](#functions:smtp_lookup_auth)
* [String](#functions:id4)
* [`chr()`](#functions:chr)
* [`ord()`](#functions:ord)
* [`str_repeat()`](#functions:str_repeat)
* [`str_replace()`](#functions:str_replace)
* [`str_split()`](#functions:str_split)
* [`str_find()`](#functions:str_find)
* [`str_rfind()`](#functions:str_rfind)
* [`str_lower()`](#functions:str_lower)
* [`str_upper()`](#functions:str_upper)
* [`str_slice()`](#functions:str_slice)
* [`str_strip()`](#functions:str_strip)
* [`str_lstrip()`](#functions:str_lstrip)
* [`str_rstrip()`](#functions:str_rstrip)
* [`str_chunk()`](#functions:str_chunk)
* [Regular expression](#functions:regular-expression)
* [`pcre_match()`](#functions:pcre_match)
* [`pcre_match_all()`](#functions:pcre_match_all)
* [`pcre_quote()`](#functions:pcre_quote)
* [`pcre_compile()`](#functions:pcre_compile)
* [`pcre_replace()`](#functions:pcre_replace)
* [Socket](#functions:socket)
* [`Socket`](#functions:Socket)
* [`Socket.constructor()`](#functions:Socket.constructor)
* [`Socket.bind()`](#functions:Socket.bind)
* [`Socket.close()`](#functions:Socket.close)
* [`Socket.connect()`](#functions:Socket.connect)
* [`Socket.errno()`](#functions:Socket.errno)
* [`Socket.recv()`](#functions:Socket.recv)
* [`Socket.send()`](#functions:Socket.send)
* [`Socket.settimeout()`](#functions:Socket.settimeout)
* [`Socket.shutdown()`](#functions:Socket.shutdown)
* [`Socket.toFFIValue()`](#functions:Socket.toFFIValue)
* [`Socket.AF()`](#functions:Socket.AF)
* [`Socket.Resource()`](#functions:Socket.Resource)
* [`TLSSocket`](#functions:TLSSocket)
* [`TLSSocket.constructor()`](#functions:TLSSocket.constructor)
* [`TLSSocket.handshake()`](#functions:TLSSocket.handshake)
* [`TLSSocket.recv()`](#functions:TLSSocket.recv)
* [`TLSSocket.send()`](#functions:TLSSocket.send)
* [`TLSSocket.shutdown()`](#functions:TLSSocket.shutdown)
* [`TLSSocket.errno()`](#functions:TLSSocket.errno)
* [`TLSSocket.getpeerx509()`](#functions:TLSSocket.getpeerx509)
* [`TLSSocket.toSocket()`](#functions:TLSSocket.toSocket)
* [`X509`](#functions:X509)
* [`X509.constructor()`](#functions:X509.constructor)
* [`X509.subject()`](#functions:X509.subject)
* [`X509.issuer()`](#functions:X509.issuer)
* [`X509.subject_alt_name()`](#functions:X509.subject_alt_name)
* [`X509.version()`](#functions:X509.version)
* [`X509.serial_number()`](#functions:X509.serial_number)
* [`X509.not_valid_before()`](#functions:X509.not_valid_before)
* [`X509.not_valid_after()`](#functions:X509.not_valid_after)
* [`X509.public_key()`](#functions:X509.public_key)
* [`X509.verify()`](#functions:X509.verify)
* [`X509.chain()`](#functions:X509.chain)
* [`X509.export()`](#functions:X509.export)
* [`X509.extensions()`](#functions:X509.extensions)
* [`X509.toResource()`](#functions:X509.toResource)
* [`X509.toFFIValue()`](#functions:X509.toFFIValue)
* [`X509.String()`](#functions:X509.String)
* [`is_x509_resource()`](#functions:is_x509_resource)
* [`X509CRL`](#functions:X509CRL)
* [`X509CRL.constructor()`](#functions:X509CRL.constructor)
* [`X509CRL.issuer()`](#functions:X509CRL.issuer)
* [`X509CRL.revoked()`](#functions:X509CRL.revoked)
* [`X509CRL.version()`](#functions:X509CRL.version)
* [`X509CRL.last_update()`](#functions:X509CRL.last_update)
* [`X509CRL.next_update()`](#functions:X509CRL.next_update)
* [`X509CRL.export()`](#functions:X509CRL.export)
* [`X509CRL.extensions()`](#functions:X509CRL.extensions)
* [`X509CRL.toResource()`](#functions:X509CRL.toResource)
* [`X509CRL.String()`](#functions:X509CRL.String)
* [`is_x509crl_resource()`](#functions:is_x509crl_resource)
* [FFI](#functions:ffi)
* [`FFI`](#functions:FFI)
* [`FFI.constructor()`](#functions:FFI.constructor)
* [`FFI.func()`](#functions:FFI.func)
* [`FFI.symbol()`](#functions:FFI.symbol)
* [`FFI.type()`](#functions:FFI.type)
* [`FFI.cnumber()`](#functions:FFI.cnumber)
* [`FFI.cstring()`](#functions:FFI.cstring)
* [`FFI.callback()`](#functions:FFI.callback)
* [`FFI.nullptr()`](#functions:FFI.nullptr)
* [`FFI.allocate()`](#functions:FFI.allocate)
* [`FFI.memcpy()`](#functions:FFI.memcpy)
* [`FFI.byref()`](#functions:FFI.byref)
* [`FFI.deref()`](#functions:FFI.deref)
* [`FFI.offset()`](#functions:FFI.offset)
* [`FFI.string()`](#functions:FFI.string)
* [`FFI.number()`](#functions:FFI.number)
* [`FFI.number64()`](#functions:FFI.number64)
* [`FFI.attach()`](#functions:FFI.attach)
* [`FFI.detach()`](#functions:FFI.detach)
* [`FFIFunction()`](#functions:FFIFunction)
* [`FFIType`](#functions:FFIType)
* [`FFIValue`](#functions:FFIValue)
* [Shared memory](#functions:shared-memory)
* [`memory_add()`](#functions:memory_add)
* [`memory_dec()`](#functions:memory_dec)
* [`memory_delete()`](#functions:memory_delete)
* [`memory_exists()`](#functions:memory_exists)
* [`memory_fetch()`](#functions:memory_fetch)
* [`memory_inc()`](#functions:memory_inc)
* [`memory_store()`](#functions:memory_store)
* [`memory_update()`](#functions:memory_update)
* [Server](#functions:server)
* [`server_concurrency()`](#functions:server_concurrency)
* [Queue](#functions:queue)
* [`queue_suspend()`](#functions:queue_suspend)
* [`queue_policy()`](#functions:queue_policy)
* [`queue_policy_delete()`](#functions:queue_policy_delete)
* [`queue_quota()`](#functions:queue_quota)
* [Metrics](#functions:metrics)
* [`metrics_increment()`](#functions:metrics_increment)
* [`metrics_decrement()`](#functions:metrics_decrement)
* [`metrics_set()`](#functions:metrics_set)
* [`metrics_get()`](#functions:metrics_get)
* [Classes](#functions:classes)
* [`Exception`](#functions:_CPPv49Exception)
* [`Exception.constructor()`](#functions:Exception.constructor)
* [`Exception.toString()`](#functions:Exception.toString)
* [`Exception.message()`](#functions:Exception.message)
* [`Exception.file()`](#functions:Exception.file)
* [`Exception.line()`](#functions:Exception.line)
* [`Exception.column()`](#functions:Exception.column)
* [SMTP server](#smtpserver)
* [Connect](#connect)
* [Variables](#connect:variables)
* [Arguments](#connect:arguments)
* [Connection](#connect:connection)
* [Transaction](#connect:transaction)
* [Functions](#connect:functions)
* [`Accept()`](#connect:Accept)
* [`Reject()`](#connect:Reject)
* [`Defer()`](#connect:Defer)
* [On script error](#connect:on-script-error)
* [On implicit termination](#connect:on-implicit-termination)
* [Proxy](#proxy)
* [Variables](#proxy:variables)
* [Arguments](#proxy:arguments)
* [Connection](#proxy:connection)
* [HELO](#proxy:helo)
* [TLS](#proxy:tls)
* [Peercert](#proxy:peercert)
* [AUTH](#proxy:auth)
* [Transaction](#proxy:transaction)
* [Recipient](#proxy:recipient)
* [Address](#proxy:address)
* [Functions](#proxy:functions)
* [`Pass()`](#proxy:Pass)
* [`Reply()`](#proxy:Reply)
* [`Next()`](#proxy:Next)
* [On script error](#proxy:on-script-error)
* [On implicit termination](#proxy:on-implicit-termination)
* [HELO](#helo)
* [Variables](#helo:variables)
* [Arguments](#helo:arguments)
* [Connection](#helo:connection)
* [TLS](#helo:tls)
* [Peercert](#helo:peercert)
* [AUTH](#helo:auth)
* [Transaction](#helo:transaction)
* [Functions](#helo:functions)
* [`Accept()`](#helo:Accept)
* [`Reject()`](#helo:Reject)
* [`Defer()`](#helo:Defer)
* [On script error](#helo:on-script-error)
* [On implicit termination](#helo:on-implicit-termination)
* [AUTH](#auth)
* [Variables](#auth:variables)
* [Arguments](#auth:arguments)
* [Connection](#auth:connection)
* [HELO](#auth:helo)
* [TLS](#auth:tls)
* [Peercert](#auth:peercert)
* [Transaction](#auth:transaction)
* [Functions](#auth:functions)
* [`Accept()`](#auth:Accept)
* [`Reject()`](#auth:Reject)
* [`Defer()`](#auth:Defer)
* [`Reply()`](#auth:Reply)
* [On script error](#auth:on-script-error)
* [On implicit termination](#auth:on-implicit-termination)
* [Authentication diagram](#auth:authentication-diagram)
* [MAIL FROM](#mailfrom)
* [Variables](#mailfrom:variables)
* [Arguments](#mailfrom:arguments)
* [Connection](#mailfrom:connection)
* [HELO](#mailfrom:helo)
* [TLS](#mailfrom:tls)
* [Peercert](#mailfrom:peercert)
* [AUTH](#mailfrom:auth)
* [Transaction](#mailfrom:transaction)
* [Address](#mailfrom:address)
* [Functions](#mailfrom:functions)
* [`Accept()`](#mailfrom:Accept)
* [`Reject()`](#mailfrom:Reject)
* [`Defer()`](#mailfrom:Defer)
* [On script error](#mailfrom:on-script-error)
* [On implicit termination](#mailfrom:on-implicit-termination)
* [RCPT TO](#rcptto)
* [Variables](#rcptto:variables)
* [Arguments](#rcptto:arguments)
* [Connection](#rcptto:connection)
* [HELO](#rcptto:helo)
* [TLS](#rcptto:tls)
* [Peercert](#rcptto:peercert)
* [AUTH](#rcptto:auth)
* [Transaction](#rcptto:transaction)
* [Recipient](#rcptto:recipient)
* [Address](#rcptto:address)
* [Functions](#rcptto:functions)
* [`Accept()`](#rcptto:Accept)
* [`Reject()`](#rcptto:Reject)
* [`Defer()`](#rcptto:Defer)
* [On script error](#rcptto:on-script-error)
* [On implicit termination](#rcptto:on-implicit-termination)
* [End-of-DATA](#eod)
* [Variables](#eod:variables)
* [Arguments](#eod:arguments)
* [Connection](#eod:connection)
* [HELO](#eod:helo)
* [TLS](#eod:tls)
* [Peercert](#eod:peercert)
* [AUTH](#eod:auth)
* [Transaction](#eod:transaction)
* [Recipient](#eod:recipient)
* [Address](#eod:address)
* [http](#eod:http)
* [Functions](#eod:functions)
* [Actions](#eod:actions)
* [`Accept()`](#eod:Accept)
* [`Reject()`](#eod:Reject)
* [`Defer()`](#eod:Defer)
* [DATA, MIME and attachments](#eod:data-mime-and-attachments)
* [`EODMailMessage`](#eod:_CPPv414EODMailMessage)
* [`EODMailMessage.queue()`](#eod:EODMailMessage.queue)
* [On script error](#eod:on-script-error)
* [On implicit termination](#eod:on-implicit-termination)
* [Disconnect](#disconnect)
* [Variables](#disconnect:variables)
* [Arguments](#disconnect:arguments)
* [Error](#disconnect:error)
* [Reply](#disconnect:reply)
* [Connection](#disconnect:connection)
* [HELO](#disconnect:helo)
* [TLS](#disconnect:tls)
* [Peercert](#disconnect:peercert)
* [AUTH](#disconnect:auth)
* [Functions](#disconnect:functions)
* [`Pass()`](#disconnect:Pass)
* [On script error](#disconnect:on-script-error)
* [On implicit termination](#disconnect:on-implicit-termination)
* [SMTP queue](#smtpqueue)
* [Pre-delivery](#predelivery)
* [Variables](#predelivery:variables)
* [Arguments](#predelivery:arguments)
* [Queue](#predelivery:queue)
* [Plugin](#predelivery:plugin)
* [Message](#predelivery:message)
* [Id](#predelivery:id1)
* [Address](#predelivery:address)
* [File](#predelivery:file)
* [DSN](#predelivery:dsn)
* [Functions](#predelivery:functions)
* [`Try()`](#predelivery:Try)
* [`Queue()`](#predelivery:Queue)
* [`Bounce()`](#predelivery:Bounce)
* [`Delete()`](#predelivery:Delete)
* [`SetMetaData()`](#predelivery:SetMetaData)
* [`GetMetaData()`](#predelivery:GetMetaData)
* [On script error](#predelivery:on-script-error)
* [On implicit termination](#predelivery:on-implicit-termination)
* [Post-delivery](#postdelivery)
* [Variables](#postdelivery:variables)
* [Arguments](#postdelivery:arguments)
* [Policy](#postdelivery:policy)
* [Policies](#postdelivery:policies)
* [Attempt](#postdelivery:attempt)
* [Result](#postdelivery:result)
* [Error](#postdelivery:error)
* [Connection](#postdelivery:connection)
* [TLS](#postdelivery:tls)
* [Peercert](#postdelivery:peercert)
* [TLSA](#postdelivery:tlsa)
* [Records](#postdelivery:records)
* [Plugin](#postdelivery:plugin)
* [DSN](#postdelivery:dsn)
* [Queue](#postdelivery:queue)
* [Plugin](#postdelivery:ai1p)
* [Message](#postdelivery:message)
* [Id](#postdelivery:id2)
* [Address](#postdelivery:address)
* [File](#postdelivery:file)
* [DSN](#postdelivery:dsn2)
* [Functions](#postdelivery:functions)
* [`Queue()`](#postdelivery:Queue)
* [`Bounce()`](#postdelivery:Bounce)
* [`Delete()`](#postdelivery:Delete)
* [`Default()`](#postdelivery:Default)
* [`SetMetaData()`](#postdelivery:SetMetaData)
* [`GetMetaData()`](#postdelivery:GetMetaData)
* [On script error](#postdelivery:on-script-error)
* [On implicit termination](#postdelivery:on-implicit-termination)
* [References](#postdelivery:references)
* [SMTP states](#postdelivery:smtp-states)
* [Integrations](#integrations)
# About
Halon’s scripting language is a domain-specific language ([DSL](http://en.wikipedia.org/wiki/Domain-specific_language)) for Halon’s [email platform](http://halon.io/). It’s used to configure the behavior of processing in a scriptable fashion.
At multiple stages in the email delivery process there are opportunities to affect the behavior (e.g. accept or reject an email message). These are referred to as *flows*, *scripts* or *contexts*, each of which extends the language with additional functions and pre-defined variables.
The syntax is inspired by languages such as [PHP](http://php.net/) (e.g. `$variable`) and [Python](http://python.org) (e.g. slices `[0:10]`). It’s however **not** based upon any of these, so in some cases even if the syntax or function name is the same, there may be differences in the behavior hence always refer to this documentation.
## Design
Here are some of the internals of Halon Scripting Language (HSL).
Compiled language
> HSL code is compiled into a binary representation. This binary representation is reused between executions.
Constant folding
> As a step in the compilation progress, different optimizations are applied. Constant folding is one of them where constant mathematical expressions are simplified (evaluated) at compile time.
Code pattern optimizations
> HSL tries to detect certain code pattern and replace them with faster alternatives. Eg. *while (true)* is compiled to the equivalent of *forever*.
Dead code elimination
> HSL tries to detect and remove dead code in the compilation stage.
Parallel execution
> HSL code may be executed in parallel. Memory may be synchronized using barriers.
Copy-on-write
> HSL features copy-on-write memory, for the data types array and string. Constants’ memory are shared among multiple executions, vastly reducing memory consumption.
R-value optimizations
> R-value expressions are optimized in a folding-style, so that unnecessary memory allocations are avoided.
Garbage collection
> HSL doesn’t require manual memory management as it features reference counted garbage collection.
Type system
> HSL is a “dynamically-typed programming language”. Featuring boolean, numbers, strings, associative arrays, functions and objects.
Modules
> HSL allows you to structure you code into reusable modules.
Classes
> HSL supports classes to encapsulate and structure data.
Scoping
> Variables are function scoped, in addition to the global scope. Functions can be either globally scoped or function scoped (however they are always registered at compile time; hence they cannot be conditionally defined). Variable scoping may be changed with the *global* keyword.
# Introduction
This language reference manual will not cover the basics of programming, instead it’s a reference manual for the language.
If you want to test out the language there is a [REPL](https://docs.halon.io/manual/cli.html#script-interpreter) available.
## Variables
Variables may store values from expression (e.g. constants or function calls) or callable function objects such as anonymous functions and named function pointers. In HSL variables are prefixed with `$` followed by `[a-zA-Z_]+[a-zA-Z0-9_]*`. Variable names are case-sensitive. Some variables are read-only, hence they are not allowed to be assigned to (primarily pre-defined variables in contexts). Variables are assigned by value ([copy-on-write](http://en.wikipedia.org/wiki/Copy-on-write)).
```hsl
$var = "foo";
$bar = $var;
$bar = "";
// $var is still "foo"
```
> **Note**
>
> Variables in HSL are main/function scoped. However a variable needs to be created in all code paths before being used. [`isset()`](#functions:isset) may test if a variable exists.
## Functions
A lot of builtin functions are available in the [function library](#functions). Functions are called by name followed by parentheses `()` with input parameters in between them. Function names are case-sensitive. The argument types must be supported by the function, otherwise an error will be raised. It’s also possible to create [user-defined functions](#structures:user-function).
```hsl
echo uptime();
echo length("Hello");
```
> **Warning**
>
> Calling a function with too few or too many arguments will raise an error, either during compilation time or at runtime (using [argument unpacking](#structures:argumentunpacking)).
## Comments
Comments may be added to the code using two syntaxes, C-style “multi-line” comments and C++ “single-line” comments.
```hsl
/*
multi-line comment
*/
// single-line comment
```
# Data types and literals
HSL has multiple data types; *strings*, *booleans*, *numbers*, *arrays* (which also works as an ordered map to store key-value pairs, similar to PHP’s [array](http://php.net/manual/en/language.types.array.php)), *objects* (created by [classes](#structures:class-statement)) and *functions* (both anonymous functions and named function pointers). Some of these data types may be represented as literals. There is also a *none* (or *null*) data type that is often used to represent errors or no valid value or response (e.g. a [return](#structures:return) statement without a value or a failed [`json_decode()`](#functions:json_decode) both of which return *none*).
## Boolean
The keywords `true` and `false` represent boolean true and boolean false, they are treated as *1* and *0* in arithmetics operations.
> **Warning**
>
> Boolean `true` and `false` should not always be used in if statement, if you are not fully aware of the [truthiness](#operators:truthtable) and loose comparison.
>
> ```hsl
> if (5 == true) { } // false: 5 is not equal to 1
> if (5) { } // true: 5 is not false, hence true
> ```
## Number
The number type is a double-precision 64-bit [IEEE 754](http://en.wikipedia.org/wiki/Double-precision_floating-point_format) value. If converted to a string it will be presented in the most accurate way possible without trailing decimal zeros. A numeric separator (`_`) is allowed between digits for readability, it does not affect the value of the number.
```hsl
echo 1.0; // 1
echo 1_000_000; // 1000000
```
> **Note**
>
> The number type can safely represent all integers between *+/-9007199254740991* (the equivalent of `(2 ** 53) - 1`).
> **Warning**
>
> After some arithmetic operations on floating point numbers; the equality (`==`) of two floating point numbers may not be true even if they mathematically “should”. This caveat is not unique to HSL, instead it is the result of how computers calculates and stores [floating point numbers](http://en.wikipedia.org/wiki/Floating_point). Arithmetic operations on *numbers* without decimals are not affected.
### Hexadecimal
Numbers may be entered in hexadecimal form (also known as base 16) using the `0x` prefix; followed by `[0-9a-f]+`. A numeric separator (`_`) is allowed between digits for readability, it does not affect the value of the number.
```hsl
echo 0xfa; // 250
echo 0x00_fa; // 250
```
### Octal
Numbers may be entered in octal form (also known as base 8) using the `0o` prefix; followed by `[0-7]+`. A numeric separator (`_`) is allowed between digits for readability, it does not affect the value of the number.
```hsl
echo 0o372; // 250
```
### Binary
Numbers may be entered in binary form (also known as base 2) using the `0b` prefix; followed by `[0-1]+`. A numeric separator (`_`) is allowed between digits for readability, it does not affect the value of the number.
```hsl
echo 0b11111010; // 250
echo 0b1111_1010; // 250
```
## String
There are two kinds of string literals, *double-quoted strings* and *raw strings*. Double-quoted strings support language features such as variable interpolation and escape sequences. Most functions (e.g. [`length()`](#functions:length) and [`str_slice()`](#functions:str_slice)) are **not** [UTF-8](http://en.wikipedia.org/wiki/UTF-8) aware, with the exception of [regular expression](#operators:regex) matching (e.g. [`pcre_match()`](#functions:pcre_match)) which may be configured to be UTF-8 aware with the [/u](#literals:patternmodifiers) modifier.
### Double-quoted string
Variable interpolation replaces `$variable` placeholders within string literals. Variables are matched in strings with the following pattern `$[a-zA-Z_]+[a-zA-Z0-9_]`. If needed there is also a more explicit syntax `${variable}` (which allows variables mid-words). Interpolating an undeclared variable raises a runtime error.
```hsl
"$variable"
"${variable}abc"
```
| Escape sequence | Meaning |
|-------------------|-------------------------------|
| `\\` | Backslash (`\`) |
| `\"` | Double quote (`"`) |
| `\$` | Dollar sign (`$`) |
| `\n` | ASCII Linefeed (LF) |
| `\r` | ASCII Carriage Return (CR) |
| `\t` | ASCII Horizontal Tab (TAB) |
| `\xhh` | Character with hex value *hh* |
### Raw string
Raw strings do not support variable interpolation nor escape sequences. This make them suitable for [regular expressions](#operators:regex). Raw strings start and end with **two single quotes** on each side `''`, with an optional delimiter in between. The delimiter can be any of `[\x21-\x26\x28-\x7e]*`; simply put any word.
```hsl
''raw string''
'DELIMITER'raw string'DELIMITER'
'#'raw string'#'
```
> **Note**
>
> There is no performance difference between double-quoted and raw strings containing the same value. However if special characters needs to be escaped then raw string are recommended for clarity.
## Regex
A regex literal is a pre-compiled regular expression object. The regular expression implementation is “Perl Compatible” (hence the function names `pcre_...`), for syntax see the [perlre](http://perldoc.perl.org/perlre.html) documentation. See supported [pattern modifiers](#literals:patternmodifiers).
```none
#/pattern/[modifiers]
```
This type can mainly be used with the [regular expression](#operators:regex) operators and also as argument to the [`pcre_match()`](#functions:pcre_match) family of functions.
```none
if ($string =~ #/hello/i) {}
if (pcre_match(#/hello/i, $string)) {}
```
### Pattern modifiers
Use pattern modifiers to change the behavior of the pattern engine, they have the capability to make the match case-insensitive and activate UTF-8 support (where one UTF-8 characters may be matched using only one dot) etc.
| Modifier | Internal define | Description |
|------------|-------------------|-------------------------------------------------------------------------|
| i | PCRE2_CASELESS | Do case-insensitive matching |
| m | PCRE2_MULTILINE | See [perl](http://perldoc.perl.org/perlre.html#Modifiers) documentation |
| u | PCRE2_UTF8 | Enable UTF-8 support |
| s | PCRE2_DOTALL | See [perl](http://perldoc.perl.org/perlre.html#Modifiers) documentation |
| x | PCRE2_EXTENDED | See [perl](http://perldoc.perl.org/perlre.html#Modifiers) documentation |
| U | PCRE2_UNGREEDY | See [perl](http://perldoc.perl.org/perlre.html#Modifiers) documentation |
| X | | Not used |
> **Warning**
>
> The `X` flag has been deprecated (since 6.7) and is now enabled by default. The `X` flag was used to enable stricter pattern validation.
## Array
An array is a very useful container; it can act as an indexed array (automatically indexed at zero, or the highest current index + 1) or as an ordered map (associative array) with any and mixed data types as key and value. The short array syntax for literal arrays `[]` is recommended.
```hsl
// indexed arrays
echo array("value", "value2");
echo ["value", "value2"];
echo [0 => "value", 1 => "value2"];
// associative arrays
echo array("key" => "value");
echo ["key" => "value"];
// multidimensional arrays
echo ["key" => ["key" => "value"]];
// automatic indexing
echo ["foo", 3=>"bar", "baz"]; // 0=>foo, 3=>bar, 4=>baz
// delete index
$foo = [];
$foo["bar"] = "hello";
unset($foo["bar"]);
echo $foo; // []
```
> **Note**
>
> Accessing any element in a zero indexed array using the *subscript* or *slice* operator is very fast (it has the complexity of *O(1)*).
## Function
Both *anonymous functions* (closures) and *named function pointers* (references to functions) are available. This datatype is primarly used to be passed as callbacks to other functions.
### Anonymous functions
An anonymous function is a unnamed [function](#structures:user-function), it can be passed as value to a function or assigned to a variable. An anonymous function can also act as a [closure](#structures:closure). The [global](#structures:global-keyword) variable scoping rules apply.
```hsl
$multiply = function ($x, $y) { return $x * $y; };
echo $multiply(3, 5); // 15
```
### Named function pointers
A named function pointer is a reference to a named function. It can reference both a [builtin function](#functions) or a [user-defined function](#structures:user-function). Prepending the function name with the [builtin](#structures:builtin-keyword) keyword works as expected.
```hsl
function length($str) { return 42; }
$function = length;
echo $function("Hello"); // 42
$function = builtin length;
echo $function("Hello"); // 5
```
## Object
An object is an instance type of a [class](#structures:class-statement) statement or of a builtin class (such as [`Socket`](#functions:Socket) or [`File`](#functions:File)).
### *class* Iterator
The builtin iterator class is used for iterators such as [generators](#structures:generator-object) or [`Map`](#functions:Map).
#### next()
Return the next value from the iterator.
* **Returns:**
iterator data
* **Return type:**
array
The iterator data returned is an associative array with two fields
> * `value` (any) the value of the iterator
> * `done` (boolean) if the iteration/iterator is completed.
> **Note**
>
> Some iterators (such as [`Map`](#functions:Map)) return an array containing a [key, value] as `value`. The foreach statement will
> then map these to its key and value variables.
## None
This data type is represented by the keyword `none`. It may be used to indicate error-result or no return value from functions such as. [`json_decode()`](#functions:json_decode) (in case of a decode error) or from a user-defined [function](#structures:user-function) with no or an empty [return](#structures:return) statement. This data type should **not** be used as an argument to other built-in functions as it yields **undefined** behavior for the most part. The only functions safe to handle this data type is:
> * [`is_number()`](#functions:is_number)
> * [`is_boolean()`](#functions:is_boolean)
> * [`is_string()`](#functions:is_string)
> * [`is_array()`](#functions:is_array)
> * [`is_function()`](#functions:is_function)
```hsl
$obj = json_decode("...");
if ($obj == none)
echo "None";
```
# Operators
Operators are used with one or more values (or expressions) which yields another value (so that the operator itself becomes an expression). Expressions can be combinations of operators, functions and variables. Below are the available operator types described. Note that operators are always part of expressions. If no parentheses are used when combining multiple operators within a statement, the expression is resolved from left to right. Therefore, the expression `2 + 2 . "test"` will return `"4test"`. The exception is multiplication, division and modulus, which has higher priority than addition and subtraction.
## Assignment
Assignments will store the value of the expression to the right of the equal sign (`=`) in a variable to the left. An expression consisting of an assignment will have the value that was assigned. This is useful when assigning a variables in e.g. [if](#structures:if) statements.
```hsl
$var = 42;
if (($random = random_number(0, 9)) > 5) echo "$random is larger than 5";
```
### Augmented assignment
Augmented assignment operators are documented in the arithmetic chapter.
```hsl
$var = 5;
$var = $var + 5; // 10
$var = 5;
$var += 5; // 10
```
> **Note**
>
> Assignments which could be written as augmented assignments are automatically optimized as such by the compiler.
> **Warning**
>
> An assignment to “self” (the same variable) on the right side of an augmented assignment yields “undefined” behavior and should not be relied upon.
>
> ```hsl
> $var = 5;
> $var += ($var = 10);
> ```
### Nullish Coalescing assignment
The nullish coalescing assignment operator is a shorthand for using the [nullish coalescing operator](#structures:null-coalescing-operator) when assigning values.
```hsl
$var = ["foo" => "bar"];
$var["foo"] ??= "default"; // "bar"
$var = [];
$var["foo"] ??= "default"; // "default"
```
### Destructuring assignment
Destructuring assignment assigns variables with values taken from an *array* value. If the value is not an *array*, all variables will be assigned *none* (rest variables defaults to an empty array) or its default value.
```hsl
[$a, $b, $c = 3] = [1, 2];
echo $a; // 1
echo $b; // 2
echo $c; // 3 // default value
```
The rest variable syntax may be used to collect the rest of the array values in a destructuring assignment. This variable has to come last in the destructuring assignment list. The type of the rest variable is always an array.
```hsl
[$a, $b, ...$c] = [1, 2, 3, 4];
echo $c; // [3, 4]
```
Keyed index assignments are also supported.
```hsl
[
"result" => $result = [],
"error" => $error = none,
"dnssec" => $dnssec = false
] = dns_query("halon.io", ["extended_result" => true]);
```
It’s however not possible to mix keyed with unkeyed assignments.
> **Note**
>
> If there is less variables in the assignment list than array values, the remaining values will be discarded. If you don’t use the rest variable syntax.
>
> ```hsl
> [$a] = [0, 1, 2];
> [$a, ...$c] = [1, 2, 3];
> ```
>
> If there is more variables in the assignment list than array values, the remaining variables will be assigned the value of *none* (rest variables defaults to an empty array) or its default value.
>
> ```hsl
> [$a, $b = 1, $c = 2] = [0];
> [$a, $b, ...$c] = [1, 2];
> ```
### Slice assignment
Slice assignments uses the [slicing](#operators:slicing) syntax to add, remove or replace items in an *array* by using the slice operator on the left side of an assigment. The slice referred to will be removed while the items on the right side of the assigment operator will be inserted in place right after the removed items.
```hsl
$var = [1, 2, 7, 5];
$var[2:3] = [3, 4]; // 1, 2, 3, 4, 5
```
## Arithmetic
These are the arithmetic operators supported, most of which operates on [numbers](#literals:number). The [operator associativity](http://en.wikipedia.org/wiki/Operator_associativity) follow the rules of most languages (e.g. C); explicit parentheses may be added to change or clarify the expression.
```hsl
$var = (3 - 2) + 2;
```
| | Operator | Augmented assignment | Augmented assignment expansion |
|----------------|------------|------------------------|----------------------------------|
| Addition | `+` | `+=` | `x = x + y` |
| Increment | `++` | | `x++` and `++x` |
| Subtraction | `-` | `-=` | `x = x - y` |
| Decrement | `--` | | `x--` and `--x` |
| Multiplication | `*` | `*=` | `x = x * y` |
| Division | `/` | `/=` | `x = x / y` |
| Modulus | `%` | `%=` | `x = x % y` |
| Exponentiation | `**` | `**=` | `x = x ** y` |
| Concatenation | `.` | `.=` | `x = x . y` |
| Precedence | Operator | Associativity |
|--------------|-------------|-----------------|
| 1 | `.` | Left to right |
| 2 | `+` `-` | Left to right |
| 3 | `*` `/` `%` | Left to right |
| 4 | `**` | Right to left |
> **Note**
>
> HSL has constant folding, so numeric calculations are done at compile-time. Which means that `3600 * 24` is just as fast as using the constant `86400`.
> **Note**
>
> The `**` operator should be used for performance instead of the [`pow()`](#functions:pow) function.
## String
Strings support the [subscript](#operators:subscript) and [slicing](#operators:slicing) operator documented in the array section.
### Concatenation
It’s possible to use the `.` concatenation operator on any data type (except `None`), in which case both operands will be casted to a string.
```hsl
echo "Hello " . "World";
echo "A number " . 5.5;
echo 1.0 . 2.5; // "12.5"
```
### Format
The string format operator `%`, allows you to interpolate values into a string using different format specifiers. On the left side of the operator is the template string and on the right side of the operator there must be an array with values.
```default
%[argument][[fill]align][width][.precision]type
```
| | |
|-----------|-------------------------------------------------------------------------|
| argument | `[` integer `]` |
| fill | `'` \ |
| align | `<` (left) \| `>` (right) \| `^` (center) \| `=` (align after sign) |
| width | integer |
| precision | integer |
| type | `s` (string) \| `f` (number) \| `x` (hex) \| `c` (char) \| `b` (binary) |
The default *argument* is the next value in the array. If an argument is specified (indexed at zero), the next implicit argument is `n + 1`. The default *fill* character is a space, but that can be changed to any characters. The default *alignment* is left with the exception for numbers which has right. For strings the *precision* acts as a cut-off point (max length). Unsupported options for a specific type is simply ignored. To print a literal `%` in a format string replace it with `%%`.
```hsl
echo "Hello %s!" % [ "World" ];
```
Example: print the [largest precisely representable integer](#literals:number) in hexadecimal, with leading zero padding up to 16-digit width.
```hsl
echo "%'016x" % [2**53 - 1];
```
Giving output of:
```console
001fffffffffffff
```
### Repeat
Strings may be repeated mutiple times using the `*` multiplication operator. It doesn’t matter on which side of the operator the multiplier is.
```hsl
echo "x" * 8; // xxxxxxxx
echo 8 * "x"; // xxxxxxxx
```
## Array
### Subscript
Single items in arrays can be accessed using the subscript (`[]`) operator. This operator may be used on variables, literals or functions.
If reading and the index doesn’t exist, `None` is returned.
```hsl
$var = ["bar", "bar"];
echo $var[2]; // not found none is returned
```
If assigning to a variable and the index is not found, the variable is converted to an array and the item is created.
```hsl
$var = [];
$var[2] = "baz";
echo $var[2]; // "baz"
```
If assigning to a variable and the subscript operator is empty `[]`, the item will be appended to the array (the variable is converted to an empty array first if needed).
```hsl
$var = [];
$var[] = "baz";
echo $var[0]; // "baz"
```
Numeric indexes are zero based. If the indexing is sequential (starting from zero) the array allows for direct access (random access) where reads and stores are done in constant O(1) time.
```default
+---+---+---+---+---+
| H | a | l | o | n |
+---+---+---+---+---+
0 1 2 3 4
```
It’s possible to chain the index operator with the [:] [slicing](#operators:slicing) operator.
It’s possible to use the spread operator as a subscript index to reference multidimensional properties.
This is supported both for reading and assigning to arrays.
```hsl
$var = ["foo" => ["bar" => 123]];
echo $var[...["foo", "bar"]]; // 123
$var[...["foo", "baz"]] = 456;
```
The following key casting rules apply.
> * Booleans are casted to numbers (0 and 1).
> * Strings (`"1"`) containing integers are casted to numbers (1).
> * Numbers (1.10) are casted to integers (1) ignoring the decimal part (x.10). 32bit signed integers are used.
> * All other values are matched as-is.
```hsl
echo ["1"=>123]; // [1=>123]
echo [1.9=>123]; // [1=>123]
echo ["1.9"=>123]; // ["1.9"=>123]
```
> **Note**
>
> Use the [`isset()`](#functions:isset) function to check if a key (index) exists in an array.
### Slicing
Slicing is done using the *[first:last:step]* operator. The indexes of each side of the : may be omitted, first index default to 0, and last index default to the length of the input, thus [:] will return a copy of the inputs values but the keys will re-indexed (numerically). The first index is inclusive and the last index is exclusive. Negative indexes are supported. If indexes causes out-of-bound, an empty type (array or string) is returned. The slicing operator works the same on arrays and strings. Indexes are counted as if the input was iterated; thus associative arrays have no special meaning. The step argument is default 1 thus returning all elements in the sequence, but it can be used to eg. every even (2) item. If the step is negative (eg -1) the sequence will be iterated backwards, causing the elements to be returned in reverse order.
```default
+---+---+---+---+---+
| H | a | l | o | n |
+---+---+---+---+---+
0 1 2 3 4 5
-5 -4 -3 -2 -1
```
```hsl
$test = "Halon";
echo $test[:]; // Halon
echo $test[1:4]; // alo
echo $test[-1:]; // n
echo $test[-3:]; // lon
echo $test[-5:-2]; // Hal
echo $test[:2] . $test[2:]; // Halon
echo "Halon"[::-1]; // "nolaH"
```
### Push and pop
| Operation | HSL | PHP |
|-------------|-------------------------------|----------------------------------|
| shift | `$array = $array[1:];` | `array_shift($array);` |
| unshift | `$array = ["item"] + $array;` | `array_unshift($array, "item");` |
| pop | `$array = $array[:-1];` | `array_pop($array);` |
| push | `$array = $array + ["item"];` | `array_push($array, "item");` |
| push | `$array = $array + "item";` | `array_push($array, "item");` |
| push | `$array[] = "item";` | `array_push($array, "item");` |
When adding two arrays together, associative keys will be merged (the first array’s data will overwritten where keys conflict) and numeric indexes will be incremented (regardless if they conflict or not).
### Removing
In order to remove specific value(s) from an array (and if push and pop is not appropriate) use the subtraction (`-`) operator to remove based on value (all value matched will be removed) and [`unset()`](#functions:unset) to remove based on a specific key (index) or slice. The subtraction operator supports both single items and arrays (where all values will be removed). The array will not be re-indexed (for that use the slice operator (`$var = $var[:]`).
```hsl
echo ["foo", 5] - 5; // [0=>"foo"]
echo ["foo", "foo", 5] - "foo"; // [2=>5]
echo ["foo", 5] - ["foo", 5]; // []
```
> **Note**
>
> Use the [`unset()`](#functions:unset) function to unset values based on the key (index) or slice.
### Repeat
Arrays may be repeated mutiple times using the `*` multiplication operator. It doesn’t matter on which side of the operator the multiplier is.
```hsl
echo ["x", "y"] * 2; // [x, y, x, y]
echo 8 * ["x", "y"]; // [x, y, x, y]
```
### Spread
The spread operator allows arrays to be expanded in place of multiple arguments in function calls and in array literals.
```hsl
$parts = ["second", "second to last"];
echo ["first", ...$parts, "last"];
// [0=>"first",1=>"second",2=>"second to last",3=>"last"]
```
It’s also possible to use the spread operator with the [subscript](#operators:subscript) operator.
## Logic (boolean)
Logic operators treats all expressions and variables as either true or false. The [truthiness](#operators:truthtable) depends on the data type.
| Test | Operator | Descriptions |
|--------|------------|----------------|
| and | and | And operator |
| or | or | Or operator |
| not | not | Not operator |
| not | ! | Not operator |
### Short-circuit evaluation
The `and` and `or` operations are short-circuit. They will only evaluate the right statement if the left one doesn’t [satisfy](http://en.wikipedia.org/wiki/Truth_table) the condition. In the example below, `bar()` is not executed because `foo()` return *true*, thus satisfying the condition.
```hsl
function foo() { return true; }
function bar() { return false; }
if (foo() or bar()) echo "foo or bar";
```
## Bitwise
Bitwise operators treat their operands as 32 bits signed integers (except for zero-fill right shift `>>>` which is unsigned) in [two’s complement](https://en.wikipedia.org/wiki/Two's_complement) format. The result of these operators are regular [numbers](#literals:number).
| Test | Operator | Descriptions |
|--------|------------|-------------------------------|
| and | & | Bitwise AND operator |
| or | \| | Bitwise OR operator |
| xor | ^ | Bitwise XOR operator |
| not | ~ | Bitwise NOT operator |
| << | << | Shift left, padded with zeros |
| >> | >> | Shift right, sign-propagating |
| >>> | >>> | Shift right, zero-fill |
```hsl
$flags = 5;
$flagA = 0b0001;
$flagB = 0b0010;
$flagC = 0b0100;
$flagD = 0b1000;
if ($flags & ($flagB | $flagC)) echo "match";
```
## Comparison
These operators compare the expressions (operands) on both sides of the operator with one another, and the expression return either true or false if they matched.
| Test | | Description | Works on types |
|-------------------------------|-----|--------------------------------------------------|------------------|
| loose equality | == | Matches for equality | Any |
| loose inequality | != | Matches for inequality | Any |
| strictly typed equality | === | Matches for strict equality | Any |
| strictly typed inequality | !== | Matches for strict inequality | Any |
| less than | < | Matches for less than | Numbers |
| greater than | > | Matches for greater than | Numbers |
| less or equal than | <= | Matches for less or equal than | Numbers |
| greater or equal than | >= | Matches for greater or equal than | Numbers |
| regular expression | =~ | Matches for equality using regular expressions | Strings |
| inequality regular expression | !~ | Matches for inequality using regular expressions | Strings |
### Loose equality table
If comparing two operands of different data type using the `==` operator, the result may be “unexpected”, therefore you should preferable always explicitly convert them using functions like [`number()`](#functions:number) and [`string()`](#functions:string).
| A \\ B | None | Boolean | Numbers | String | Vector | Function | Object | Resource |
|--------------|--------|------------------|-----------------|------------------|------------------|------------------|------------------|------------------|
| **None** | true | false | false | false | false | false | false | false |
| **Boolean** | false | A === B | number(A) === B | A === boolean(B) | A === boolean(B) | A === boolean(B) | A === boolean(B) | A === boolean(B) |
| **Numbers** | false | A === number(B) | A === B | A === number(B) | A == boolean(B) | A == boolean(B) | A == boolean(B) | A == boolean(B) |
| **String** | false | boolean(A) === B | number(A) === B | A === B | false | false | false | false |
| **Vector** | false | boolean(A) === B | boolean(A) == B | false | A === B | false | false | false |
| **Function** | false | boolean(A) === B | boolean(A) == B | false | false | A === B | false | false |
| **Object** | false | boolean(A) === B | boolean(A) == B | false | false | false | A === B | false |
| **Resource** | false | boolean(A) === B | boolean(A) == B | false | false | false | false | A === B |
### Truthiness
Truthiness of a value tells if the value is considered `true` eg. when using them as conditions in [if statements](#structures:if).
| Type | Truthiness |
|--------------|--------------|
| **None** | false |
| **Boolean** | x |
| **Numbers** | x != 0 |
| **String** | ! empty(x) |
| **Vector** | ! empty(x) |
| **Function** | true |
| **Object** | true |
| **Resource** | true |
### Regular expression
The regular expression operator (`=~` and not-match `!~` operator) matches a string by default using partial matching against a [regular expression](#literals:regextype). That means it allows a substring to match. To explicit mark the beginning or end of a pattern, use `^` for beginning and `$` for the end.
```none
if ($var =~ #/\bhalon\b/) echo "contain the word halon";
```
> **Note**
>
> If using [raw strings](#literals:rawstring) with regular expressions there is no need to escape some characters twice. Literal strings (both [double-quoted](#literals:doublequoted) (without variable interpolation) and [raw strings](#literals:rawstring)) as regular expressions will be precompiled for greater performance.
> **See also**
>
> For data extraction using regular expressions see [`pcre_match()`](#functions:pcre_match) family of functions.
## Function
### Call
Functions may be [called](#structures:function-calling) using the `()` operator. It applies to both regular functions as well as anonymous functions and named function pointers.
```hsl
$multiply = function ($x, $y) { return $x * $y; };
echo $multiply(3, 5); // 15
```
## Class
### Property
The property operator (`->`) may be used to access (non-static) variables and functions on objects (class instances). It acts the same as the [subscript](#operators:subscript) operator (`[]`).
```hsl
class makeCounter
{
constructor() { $this->n = 0; }
function inc() { $this->n += 1; }
function get() { return $this->n; }
}
$counter1 = makeCounter();
$counter1->inc(); // 1
$counter1["inc"](); // 2
echo $counter1->get(); // prints 2
```
### Scope resolution
The scope resolution operator (`::`) is used to access static variables and functions on [classes](#structures:class-statement).
```default
class-name :: function
class-name :: $variable
```
```hsl
class MyClass
{
static $x = 5;
static function getX() { return MyClass::$x; }
}
echo MyClass::$x; // 5
echo MyClass::getX(); // 5
```
#### Static
The scope resolution operator can use the `static` keyword in the same class as a shorthand for the class name itself.
```hsl
class MyClass
{
static $x = 5;
static function getX() { return static::$x; }
}
```
# Control structures
A few language constructs are available in HSL in order to control program flow.
## echo
The `echo` statement will convert the expression to a string and print its value.
```default
echo expression ;
```
```hsl
echo "hello world";
```
## if
One of the most basic control structures in HSL is the *if* statement, it allows conditional control flow. *if* statements check the expression for [truthiness](#operators:truthtable).
```default
if (condition)
statement
```
In addition to *if* there is also an *else* branch available; executed if the expression yields false
```default
if (condition)
statements
else
statements
```
```hsl
if (true) {
echo "statement is true";
}
```
### Ternary operator
The ternary operator is an expression and allows *expression branching*, if the *if_true_expression* is omitted (Elvis operator) then the value of *expression* is used if it tests positive for truthiness, also the expression is not re-evaluated.
```default
expression ? if_true_expression : if_false_expression
expression ? : if_false_expression
```
The ternary operator is right-associative with makes them stackable like if-else statements.
```hsl
$var = isset($arg) ? $arg : "default value";
```
### Null coalescing operator
The null coalescing operator is an expression and allows *expression branching*, if the *variable* tests positive for [`isset()`](#functions:isset) and is not a value of None (null) it is used. The expression which makes up the variable is not re-evaluated. There is also a [nullish coalescing assigment operator](#operators:null-coalescing-assignment).
```default
variable ?? if_false_expression
```
The null coalescing operator is right-associative with makes them stackable like if-else statements.
```hsl
$data = json_decode(...);
$var = $data["settings"] ?? "default value";
```
## for
*for* statements allows conditional loops.
```default
for ( [initialization] ; [condition] ; [final-expression] )
statements
```
```hsl
for ($i = 0; $i < 10; $i++) {
echo $i;
}
```
### break
*break* will abort the loop iteration of a *for*, *foreach*, *while* and *forever* loop and also the execution of *switch* statements.
### continue
*continue* will abort the current loop iteration of a *for*, *foreach*, *while* and *forever* loop, and restart on the next iteration.
## foreach
*foreach* loops allows iteration on array values, object members (public and private depending on the execution context) and [`Iterator`](#literals:Iterator) to execute the same statements multiple times.
```default
foreach (expression as $val)
statements
foreach (expression as $key => $val)
statements
```
```hsl
foreach (["Apple", "Banana", "Orange"] as $fruit) {
echo $fruit;
}
```
### break
*break* will abort the loop iteration of a *for*, *foreach*, *while* and *forever* loop and also the execution of *switch* statements.
### continue
*continue* will abort the current loop iteration of a *for*, *foreach*, *while* and *forever* loop, and restart on the next iteration.
## while
*while* statements allows conditional loops.
```default
while ( expression )
statements
```
```hsl
$i = 0;
while ($i < 10) {
echo $i;
$i += 1;
}
```
### break
*break* will abort the loop iteration of a *for*, *foreach*, *while* and *forever* loop and also the execution of *switch* statements.
### continue
*continue* will abort the current loop iteration of a *for*, *foreach*, *while* and *forever* loop, and restart on the next iteration.
## forever
*forever* statements allows indefinite loops.
```default
forever
statements
```
```hsl
$i = 0;
forever {
echo $i;
$i += 1;
}
```
### break
*break* will abort the loop iteration of a *for*, *foreach*, *while* and *forever* loop and also the execution of *switch* statements.
### continue
*continue* will abort the current loop iteration of a *for*, *foreach*, *while* and *forever* loop, and restart on the next iteration.
## switch
*switch* statements are in many ways similar to nested if-else statements. *case* expressions are compared to the *switch* expression until a match is found. If no match is found, and a *default* label exists, it will be executed.
```default
switch (expression) {
case expression:
statements
break;
case expression:
statements
break;
default:
statements
break;
}
```
If executing a statement and *break* is omitted the control flow will fall-through to the next statement.
## match
*match* expressions are in many ways similar to *switch* statements, however *match* is an expression and must result in a value.
If no match, and no optional *default* label exists an exception will be thrown.
Each match expressions may include multiple expression to be compared to seperated by a comma.
```default
$variable = match (expression) {
compare-expression => value-expression;
compare-expression, compare-expression => value-expression;
default => value-expression;
}
```
```hsl
echo match (5) {
0, 2, 4, 6, 8 => "even",
1, 3, 5, 7, 9 => "odd"
};
```
## include
The *include* statement allows code to be structured in logical modules and shared between different scripts. The include path can be any of the supported *file* path formats (`file:X` where `file:` is implicit). *include*’s file name argument do not support variable interpolation nor expression since the include needs to be resolved at compile time. The statements in the included file are included in-place (replacing the *include* statement itself).
```default
include string ;
```
```hsl
include "file:1";
include "1";
```
> **Note**
>
> The same file may be included multiple times. However cyclic inclusion is not permitted.
### include_once
The *include_once* keyword will only include the file if it hasn’t been included before.
```default
include_once string ;
```
## import
The *import* statement allows code to be structured in logical modules and shared between different scripts very much like *include_once* with the difference that all symbols which should be used in the calling script has to be explicitly imported. Also instead of running the imported code directly it is executed in a seperate context (with its own function and variable symbol table) referred to as “the module’s global scope”. If a file is imported multiple times (regardless of the symbols imported) its code will only be executed once (a behaviour which could be used to initialize global state), very much like *include_once* would behave. All symbols in a module’s symbol table is exported (by default), that include symbols which the module itself has imported from another module (a.k.a forwarding imports). An import can not be conditionally and must be defined unconditionally in the script (that usually means at the top of a script).
```default
import { symbol [ as symbol ] [ , ... ] } from string;
import * as symbol from string;
import variable from string [ with [ options ] ];
```
```hsl
import { foo, bar as baz, $x as $y } from "module";
import { $x as $y } from "module";
```
> **Note**
>
> * The same file may be imported multiple times, but it will only be executed once. However cyclic inclusion is not permitted.
> * The path for the module may point to a folder, if so the file `main.hsl` in that folder is used.
> * The special syntax `./file.hsl` refer to a file in the same folder as the current file.
### variables
A variable in the module’s global scope may be imported into the global scope. An imported variables is imported by reference (and not by value), hence all changes to the variable in the module will be reflected by the imported variable. An import statement is not allowed to overwrite variables in the local scope (if a conflict occures, it should be imported under another name).
```hsl
import { $x, $y as $z } from "module";
```
### functions
A function in the module’s global scope may be imported into the global scope. An imported function (when executed) is executed in the module’s global scope. Hence, the *global* keyword imports from the module’s global context.
```hsl
import { v1, v2, v2 as vLatest } from "module";
```
### wildcard
Wildcard imports (`*`) allows you to import all variables and functions from a module to a namespace (static class).
```hsl
import * as foo from "foo";
foo::bar();
echo foo::$x;
```
### data
All content in a module/file can be imported as a variable using different import methods at compile time (chosen by file extensions), this has the benefit of doing the data import once and the data shared across all executions.
```hsl
import $config from "config.json";
import $lookup from "lookup.csv";
import $data from "data.txt" with [ "type" => "array" ];
```
Currently there are data imports for the following file extensions.
| Extension/Loader | Transformation | Import type |
|--------------------|---------------------|---------------------|
| json | json_decode | any |
| yaml | yaml_decode | any |
| csv | csv_decode | array |
| txt | *str_split* | string/array/Set |
| crt | X509::String | array of X509 |
| crl | X509CRL::String | array of X509 CRL |
| eml | MailMessage::String | MailMessage |
| key | \*_privatekey | Privatekey resource |
While there are a lot of file extensions that do not have a loader (in the supported list above), it’s still possible to import any file using the syntax of *loader!file.ext* to override the selection of loader by *file extension* for that file (eg. `txt!logo.png` or `json!config.cfg`).
The .json import behaviour can be changed with the following options, default is to allow comments.
> * **objects** (boolean) If JSON objects should imported as objects (instead of arrays). The default is `false`.
The .yaml import behaviour can be changed with the following options.
> * **types** (array) Add support for the following predefined “tag” imports.
>
> | Type | Transformation | Import type |
> |---------------|-----------------------|---------------------|
> | `Map` | Map() | Map |
> | `Set` | Set() | Set |
> | `Regex` | pcre_compile() | Regex resource |
> | `PrivateKey` | \*_privatekey() | Privatekey resource |
> | `X509` | X509::String() | X509 |
> | `X509CRL` | X509CRL::String() | X509CRL |
> | `MailMessage` | MailMessage::String() | MailMessage |
A sample import with types could look like this
```hsl
import $data from "myfile.yaml" with ["types" => ["Set", "Map", "Regex"]];
```
With the YAML tags defined as the following
```yaml
my-set: !Set
- item1
- item2
- item3
my-map: !Map
item1: !Regex /^pattern1$/
item2: !Regex /^pattern2$/
```
The .csv import behaviour can be changed with the following options.
> * **delimiter** (string) The format separator. The default is `,`.
> * **header** (boolean) If the CSV data includes a header. The default is `true`.
> * **schema** (array) A [`csv_decode()`](#functions:csv_decode) compatible CSV schema.
The .txt import behaviour can be changed with the following options.
> * **type** (string) Import the file line by line as an `array` (array type) or `set` ([`Set`](#functions:Set) type) (without the CRLF or LF delimiter). The default is `string`.
The .crt import behaviour can be changed with the following options.
> * **resource** (boolean) Import the certificate as a `X509Resource` instead of `X509` object. The default is `false`.
The .crl import behaviour can be changed with the following options.
> * **resource** (boolean) Import the CRL as a `X509CRLResource` instead of `X509CRL` object. The default is `false`.
The .eml import behaviour can not be changed.
The .key import behaviour can not be changed.
> **Warning**
>
> When import object such as `Map`, `Set` or `MailMessage`. Do *not* modify these objects. As these objects are shared between all script execution. And this object and operations are not thread-safe.
#### wildcard file imports
For all data imports, `glob` or sometimes called `fnmatch` patterns are supported. This allows you to import multiple resources into the same variable (supporting both id and `file://` matching). The resulting import will be an array where the data index on the file name.
```hsl
import $certs from "file://keys/*.crt";
// ["file://keys/1.crt" => X509, "file://keys/2.crt" => X509, ...]
```
## function
It’s possible to write new functions in HSL, and also to override builtin [functions](#functions). A function may take any number of arguments and return a value using the [return](#structures:return) statement. If non-variadic arguments are specified, the number of argument given by the caller must match the number of required arguments in the function definition.
```default
function funcname() {
return expression;
}
function funcname($arg1, $arg2) {
return expression;
}
function funcname(...$argv) {
return expression;
}
```
> **Warning**
>
> Recursion is not allowed.
### Named functions
A function may be named (in order to be callable by its name) according to the regular expression pattern `[a-zA-Z_]+[a-zA-Z0-9_]*` with the exception of reserved keywords. In order to prevent naming conflicts in the future with added reserved keywords; it may be a good idea to prefix the function name with a unique identifier like `halon_func`.
`and` `array` `as` `barrier` `break` `builtin` `cache` `case` `class` `closure` `constructor` `continue` `default` `echo` `else` `false` `for` `foreach` `forever` `from` `function` `global` `if` `import` `include` `include_once` `isset` `match` `not` `none` `object` `or` `private` `readonly` `return` `switch` `true` `unset` `while` `with`
You *should* avoid using keywords available in other general purpose languages and they may be added in the future. That includes keywords such as *for*, *this*, *protected*, *public* etc.
#### Function scope
Named functions are scoped either in the global scope (if not defined inside another function) or function scoped (a nested scope, may access functions in the previous scope). They are unconditionally registered at compile-time (control flow is not taken into consideration). Hence it doesn’t matter where in the scope it’s defined (eg. before or after it’s being called).
```hsl
funcname("World");
function funcname($name) {
echo "Hello $name";
}
```
> **Note**
>
> Named functions are “hoisted”.
### Anonymous functions
The syntax for [anonymous functions](#literals:anonymous-functions) are the same as for named functions, with the exception that the function name is omitted. Hence they must be called by their value and not by name.
```default
function (argument-list) {
return expression;
};
```
```hsl
$variable = function ($name) {
echo "Hello $name";
};
$variable("World");
```
> **Note**
>
> An anonymous function may be used as an *immediately-invoked function expression* (IIFE), meaning it may be invoked directly.
>
> ```hsl
> echo function ($name) {
> return "Hello $name";
> }("World");
> ```
### Closure functions
The difference between an anonymous function and a closure function is that a closure function may capture (close over) the environment in which it is created. An anonymous function can be converted to a closure by adding the *closure* keyword followed by a capture list after the function argument list. These variables are captured by reference from the parent scope (function or global) in which they are created.
```default
function (argument-list) closure (variable-list) {
return expression;
};
```
Most languages which implement closures capture (closes over) the entire scope (doesn’t use the concept of a capture list). HSL does not with the reasoning that all variables are function local; if the entire scope were to be closed over ambiguities could easily arise, and secondly it allows the developer to explicitly state the intention of the code.
```hsl
function makeCounter() {
$n = 0;
return [
"inc" => function () closure ($n) { $n += 1; },
"get" => function () closure ($n) { return $n; },
];
}
$counter1 = makeCounter();
$counter2 = makeCounter();
$counter1["inc"]();
echo $counter1["get"](); // 1
echo $counter2["get"](); // still 0, $counter2 hasn't been updated
```
> **Note**
>
> This feature is similar to the PHP implementation of closures (*use*) however HSL’s *closure* statement captures by reference.
In order to capture by value, the following *immediately-invoked function expression* (IIFE) pattern may be used.
```hsl
$i = 3;
$f = function ($i) { return function () closure ($i) { return $i * $i; }; } ($i);
$i = 10;
echo $f(); // 3 * 3 = 9
```
### return
The *return* statement return a value from a function. If the expression is omitted a value of *none* is returned.
```default
function funcname() {
return [ expression ];
}
```
```hsl
function funcname() {
return 42;
}
```
> **Note**
>
> If the *return* statement is omitted and execution reached the end of the function, a value of *none* is returned. This is fine if the function is a *void* function.
### Default argument
Formal parameters may be initialized with a default value if not given by the caller. Default values may only defined as trailing parameters in the function definition. Constant expressions which can be evaluated during compile-time may be used as default values (e.g. `$a = 10 * 1024` and `$a = []`).
```default
function funcname($arg1 = constant_expressions) {
statements
}
```
```hsl
function hello($name = "World") {
return "Hello $name.";
}
echo hello(); // Hello World.
echo hello("You"); // Hello You.
```
### Variadic function
Arbitrary-length argument lists are supported using the `...$argument` syntax when declaring a function, the rest of the arguments which were not picked up by an other named argument will be added to the last variable as an array. This variable has to be defined at the end of the argument list.
```default
function funcname($arg1, ...$argN) {
statements
}
```
```hsl
function avg(...$values) {
$r = 0;
foreach ($values as $v)
$r += $v;
return $r / length($values);
}
$values = [0, 5, 10, 15];
echo avg(...$values);
```
### global
The *global* statement allows variables to be imported in to a local function scope (by reference). If the variable is not defined at the time of execution (of the global statement) it will simply be marked as “global” and if later assigned; written back to the global scope once the function returns. If the variable that is imported to the function scope already exists in the function scope an error will be raised. If an imported variable is read-only, it will be read-only in the function scope as well.
```default
function funcname() {
global $variable[, $variable [, ...]];
}
```
```hsl
function Accept() {
echo "Message accepted";
builtin Accept();
}
Accept();
```
### Function calling
#### Argument unpacking
Argument unpacking make it possible to call a function with the arguments unpacked from an array at runtime, using the *spread* or *splat* operator (`...`). The calling rules still apply, the argument count must match. This make it easy to override function.
```default
funcname(...expression)
$variable(...expression)
```
#### builtin
The *builtin* statement allows you to explicitly call the builtin version of an overridden function.
```default
builtin funcname()
builtin funcname
```
```hsl
function length($str) {
echo "length called with $str";
return builtin length($str);
}
echo length("hello");
```
## `function*`
The `function*` statement declares a generator function, which returns a generator object. A generator object is an instance of a function that may *yield* or *return* multiple values. Generator functions support most of the function features available (eg. anonymous functions, default argument etc.). This manual may not be the best place to learn about generator functions in general as it more or less only describes our implementation of them.
```default
function* name() {
yield or return
}
```
### generator object
A generator object ([`Iterator`](#literals:Iterator)) is return when a generator function is called. It has one *next* method which invokes or resumes the generator function and returns the value of the next *yield* or *return* statement. If *yield* is used the function is suspended and the value from the yield statement is return. If *return* is used, the value is returned and the generator is finished (it may not be resumed).
> * **value** (any) The value returned from the generator function
> * **done** (boolean) The current state of the generator function
```hsl
function* infinity() {
$i = 1;
while (true)
yield $i++;
}
$x = infinity();
echo $x->next(); // ["value"=>1,"done"=>false]
echo $x->next(); // ["value"=>2,"done"=>false]
```
Generator objects may be used with the `foreach` statement to iterate all its values, then the *value* will be the iteration variable of the foreach.
```hsl
function* counter($max) {
$i = 1;
while ($i <= $max)
yield $i++;
}
$x = counter(5);
foreach ($x as $i)
echo $i;
```
### yield
The yield statement may be used to return multiple values from a generator function. If *return* is used within a generator function instead of *yield* the generator function is done and cannot be resumed with the *next* method.
```hsl
function* countdown() {
yield 3;
yield 2;
return 1;
yield 0;
}
$x = countdown();
echo $x->next(); // ["value"=>3,"done"=>false]
echo $x->next(); // ["value"=>2,"done"=>false]
echo $x->next(); // ["value"=>1,"done"=>true]
echo $x->next(); // ["value"=>,"done"=>true]
```
When the *yield* keyword is used as a expression, its value will be the generator objects *next* function argument. It may still yield a value.
```hsl
function* hello() {
while (true)
echo "Hello " . yield;
}
$x = hello();
$x->next(); // needed by pattern
$x->next("Alice");
$x->next("Bob");
```
## Exceptions
The functionality of throwing and catching exceptions provide a mechanism to disrupts the normal execution flow on eg. error conditions.
### try
```default
try {
code
} catch (variable) {
code
}
```
In order to catch an exception, it is has to be thrown within a *try*/*catch* code block. When a exception is thrown the remaining
code in the *try* statement is not execution and the execution flow is moved to the first statement of the *catch* code block.
Exception are catched at the first *catch* statement in the callstack, and are not propegated unless re-thrown.
If and exception is thrown outside of a *try*/*catch* code block it will result in a script error.
```hsl
try {
echo "Hello";
throw Exception("Error");
echo "You"; // Never executed
} catch ($e) {
echo "World";
}
```
### catch
The caught exception is available in the variable specificed in the catch statement.
```hsl
try {
// throw here
} catch ($e) {
echo $e;
}
```
### throw
Any value type may be thrown. Built-in runtime exceptions are instances of the [`Exception`](#functions:_CPPv49Exception) class.
```hsl
try {
throw "Hello World";
} catch ($e) {
echo $e;
}
```
> **Note**
>
> If throwing custom classes, any available toString() method may be called when logging the error.
## class
The *class* statement can be used to declare new types of classes. The *class-name* must be a valid function name. In order to create a new instance of a class (object) call the class by name using the function calling convention. Class instances (objects) are not copy on write and all copies reference the same object. The default visibility of class members are public.
```default
class class-name
{
constructor() {}
$variable = initial-value;
function function-name() {}
private $variable = initial-value;
private function-name() {};
readonly $variable = initial-value;
static $variable = initial-value;
static function function-name() {}
private static $variable = initial-value;
private static function function-name() {}
}
```
> **Note**
>
> Names of functions and variables may not conflict, as it will cause a compile error.
### constructor
The constructor (function) is a special function declared inside the class statement. This function (if it exist) is called when an object is created, all arguments from the class-name calling is passed to the constructor function. The constructor function supports all features of function calling (such as default arguments). The constructor is usually used to initialize object instance variables on the special `$this` variable.
```hsl
class Foo
{
constructor($a, $b = 5) { $this->a = $a; }
}
$x = Foo(5);
```
> **Note**
>
> There is no destructor. Objects are destructed (garbage collected) when they aren’t referenced by anyone.
### Instance
An instance of a class is created by calling the name of the class (hence calling the constructor). This will create a special `$this` variable bound to the object. Property and method access is done with the [property access](#operators:propertyoperator) operator (`->`) or [subscript](#operators:subscript) operator (`[]`).
#### $this
The `$this` variable is a private instance variable that can only be used within a class function and is used to access the
instance methods and variables (private and public). This variable is iterable using the [foreach](#structures:foreach) structure. If returned from a class function it will reference the public interface of the instance.
```hsl
class Foo
{
$value = 0;
// serialize data members...
function serialize() {
$data = [];
foreach ($this as $k => $v)
if (!is_function($v))
$data[$k] = $v;
return $data;
}
// unserialize data members
function unserialize($data) {
foreach ($data as $k => $v)
$this[$k] = $v;
return $this;
}
}
$x = Foo();
$x->value = 123;
$export = json_decode(json_encode($x->serialize()));
$y = Foo()->unserialize($export);
echo $y->value;
```
#### variables
An instance variable is either defined in the class body or created on the `$this` object (variable) in the constructor function. At any time, new properites may be added and removed on the `$this` object.
```hsl
class Foo
{
$y = 5;
constructor() { $this->x = 5; }
}
$x = Foo();
echo $x->x;
echo $x->y;
```
#### functions
A instance function is a function declared in a class statement and is only available on object instances. On execution it has access to the object’s `$this` variable.
```hsl
class Foo
{
function setX() { $this->x = 5; }
}
$x = Foo();
echo $x->setX();
```
### Static
A static function or variable is not bound to a class instance instead they are only scoped by the class name using the [scope resolution](#operators:scopeoperator) operator (`::`). Static members are not available on instance objects.
#### variables
A static variable is declared within a class statement using the *static* keyword. A static variable is namespaced to the scope of the class name and it’s initialized at compile time (but can be updated and used at runtime). A static variable can only be initialized to a constant expressions which can be evaluated during compile-time.
```hsl
class Foo
{
static $x = 10;
}
echo Foo::$x;
```
#### functions
A static function is declared within a class statement using the *static* keyword. A static function is namespaced to the scope of the class name. On execution it does not has access to a `$this` variable. Instead to hold state, a static function usually use static class variables.
```hsl
class Foo
{
static $x = 10;
static function getX() { return Foo::$x; }
}
echo Foo::getX();
```
### Visibility
The default visibility of class members are public. However both instance- and static -variables and functions can be declared as private.
#### private
Variables and functions may be declared as private, in which case they can only be accessed from within other function on the same instance or class.
```hsl
class Foo
{
function publicAPI() { $this->do(); }
private function do() { }
static function publicAPI2() { Foo::do2(); }
private static function do2() { }
}
$x = Foo();
$x->publicAPI();
Foo::publicAPI2();
```
### Permissions
The default permissions of public class variable allows for read and write access.
#### readonly
Variables may be declared as readonly, in which case they can only be written to from within function on the same instance.
```hsl
class Foo
{
readonly $count = 0;
function inc() { $this->count += 1; }
}
$x = Foo();
$x->inc();
echo $x->count; // 1
$x->count = 2; // Error
```
## cache
The *cache* statement can be prepended to any named function call. If the same call is done and the result is already in its cache the function will not be executed again, instead the previous result will be used. The cache take the function name and argument values into account when caching. In case of concurrent cache misses, the consecutive request will wait for the first to complete and use its result.
```default
cache [ cache-option [, cache-option [, ...]]] [builtin] funcname()
```
The following cache options are available.
> > * **ttl** (number, none) Time to Live (TTL) in seconds for the cache entry if added to the cache during the call. If *ttl* is `none` the cache will not expire based on time. The default time is `60` seconds.
> > * **ttl_override** (array) An associative array where the key is the *return value* and the value is the overridden *ttl* to be used.
> > * **ttl_function** (function) A function taking one argument (the function’s *return value*) and returning the *ttl* to be used.
> > * **update_function** (function) A function called at cache updates; taking two arguments (the *old* and *new* value) and returning the value to be used and cached. Using this function will disable the active TTL expiration (since it needs to keep the old value), the cache size will grow up to the size limit.
> > * **insert_function** (function) A function called at cache insert; taking one arguments (the *new* value) and returning the value to be used and cached.
> > * **miss_function** (function) If configured a function called at cache misses instead of the actual function, the returning the value will be the result of the call.
> > * **argv_filter** (array) A list of argument indexes (starting at 1) which should make this cache entry unique. The default is to use all arguments.
> > * **force** (boolean) Force a cache-miss. The default is `false`.
> > * **size** (number) The size of the cache (a cache is namespace + function-name). The default is `32`.
> > * **namespace** (string) Custom namespace so that multiple caches can be created per function name. The default is an empty string.
> > * **lru** (boolean) If the cache is full and a cache-miss occur it will remove the Least Recently Used (LRU) entry in order to be able to store the new entry. The default is `true`.
>
> ```hsl
> // cache both the json_decode() and http() request
> function json_decode_and_http(...$args) {
> return json_decode(http(...$args));
> }
> $list = cache [] json_decode_and_http("http://api.example.com/v1/get/list");
> ```
The *\_function* arguments will be called in the following order.
* On initial cache call
1. ttl_function
2. insert_function
* On cache update
1. ttl_function
2. update_function
> **Warning**
>
> Not all functions should be cached. If calls cannot be distinguished by their arguments or if they have side-effects (like Defer), bad things will happen.
>
> ```hsl
> // Defer messages on Sundays
> if (cache [] strftime("%u") == "7") // The same (and incorrect) day will be used for all days messages
> cache [] Defer(); // if Sunday, Defer may still on happen once for one single message...
> Accept(); // ...and all other messages will be delivered.
> ```
> **Note**
>
> By default (if not distinguish by *namespace*), all cached calls to the same function name share the same cache bucket, consequently the cache statement with the smallest size set the effective max size for that cache. It’s recommended to use different *namespaces* for unrelated function calls.
> **Note**
>
> The process cache scope (*scripting.cache.scope*) can either be *config* (cleared on configuration reload, default), or *global* (persistent).
> If the *ttl* is `none`, the cache will not expire and can only be cleared manually, this is useful for eg. doing one-time parsing of imported data on configuration reloads.
## barrier
A *barrier* is *named* mutually exclusive scope, only one execution is allowed to enter the same named scope (applies to all thread). Waiters are queued for execution in random order. Optionally with every barrier comes a shared variable (*shared memory*) which data is shared among executions. Barries share the same memory and lock as the [memory](#functions:shared-memory) functions.
```default
barrier statement {
statements
}
barrier statement => variable {
statements
}
```
```hsl
barrier "counter" => $var {
$var = isset($var) ? $var : 0;
echo $var;
$var += 1;
}
```
# Standard library
Functions which are documented in this chapter are considered *core* functions hence are available in all *contexts*. Functions in the standard library may be recognized by the fact that they are all in lowercase.
* **Array** [`array_combine()`](#functions:array_combine) [`array_every()`](#functions:array_every) [`array_filter()`](#functions:array_filter) [`array_find()`](#functions:array_find) [`array_includes()`](#functions:array_includes) [`array_join()`](#functions:array_join) [`array_keys()`](#functions:array_keys) [`array_map()`](#functions:array_map) [`array_random()`](#functions:array_random) [`array_range()`](#functions:array_range) [`array_reduce()`](#functions:array_reduce) [`array_reverse()`](#functions:array_reverse) [`array_sort()`](#functions:array_sort) [`array_shuffle()`](#functions:array_shuffle) [`array_unique()`](#functions:array_unique)
* **Cryptographic** [`aes_decrypt()`](#functions:aes_decrypt) [`aes_encrypt()`](#functions:aes_encrypt) [`hmac_md5()`](#functions:hmac_md5) [`hmac_sha1()`](#functions:hmac_sha1) [`hmac_sha2()`](#functions:hmac_sha2) [`md5()`](#functions:md5) [`sha1()`](#functions:sha1) [`sha2()`](#functions:sha2) [`hash()`](#functions:hash) [`pkcs11_load()`](#functions:pkcs11_load) [`rsa_privatekey()`](#functions:rsa_privatekey) [`is_rsa_privatekey()`](#functions:is_rsa_privatekey) [`rsa_sign()`](#functions:rsa_sign) [`rsa_verify()`](#functions:rsa_verify) [`ec_privatekey()`](#functions:ec_privatekey) [`is_ec_privatekey()`](#functions:is_ec_privatekey) [`ed25519_privatekey()`](#functions:ed25519_privatekey) [`is_ed25519_privatekey()`](#functions:is_ed25519_privatekey) [`ed25519_sign()`](#functions:ed25519_sign) [`ed25519_verify()`](#functions:ed25519_verify) [`pkcs7_sign()`](#functions:pkcs7_sign) [`cms_sign()`](#functions:cms_sign) [`cms_verify()`](#functions:cms_verify) [`cms_encrypt()`](#functions:cms_encrypt) [`cms_decrypt()`](#functions:cms_decrypt) [`cms_type()`](#functions:cms_type) [`cms_recipients()`](#functions:cms_recipients) [`random_bytes()`](#functions:random_bytes) [`random_number()`](#functions:random_number) [`crypt()`](#functions:crypt)
* **Data types** [`length()`](#functions:length) [`array()`](#functions:id0) [`boolean()`](#functions:boolean) [`number()`](#functions:number) [`string()`](#functions:string) [`is_array()`](#functions:is_array) [`is_boolean()`](#functions:is_boolean) [`is_function()`](#functions:is_function) [`is_number()`](#functions:is_number) [`is_object()`](#functions:is_object) [`is_string()`](#functions:is_string) [`isset()`](#functions:isset) [`unset()`](#functions:unset) [`Map`](#functions:Map) [`Set`](#functions:Set)
* **Date and time** [`executiontime()`](#functions:executiontime) [`sleep()`](#functions:sleep) [`strftime()`](#functions:strftime) [`strptime()`](#functions:strptime) [`strpduration()`](#functions:strpduration) [`time()`](#functions:time) [`timelocal()`](#functions:timelocal) [`uptime()`](#functions:uptime)
* **DNS** [`dns_query()`](#functions:dns_query) [`domain_includes()`](#functions:domain_includes) [`domain_publicsuffix()`](#functions:domain_publicsuffix) [`idna_encode()`](#functions:idna_encode) [`idna_decode()`](#functions:idna_decode)
* **Encodings and JSON** [`base32_encode()`](#functions:base32_encode) [`base32_decode()`](#functions:base32_decode) [`base64_encode()`](#functions:base64_encode) [`base64_decode()`](#functions:base64_decode) [`base64url_encode()`](#functions:base64url_encode) [`base64url_decode()`](#functions:base64url_decode) [`zlib_compress()`](#functions:zlib_compress) [`zlib_uncompress()`](#functions:zlib_uncompress) [`csv_encode()`](#functions:csv_encode) [`csv_decode()`](#functions:csv_decode) [`json_encode()`](#functions:json_encode) [`json_decode()`](#functions:json_decode) [`url_encode()`](#functions:url_encode) [`url_decode()`](#functions:url_decode) [`mimeword_encode()`](#functions:mimeword_encode) [`mimeword_decode()`](#functions:mimeword_decode) [`yaml_decode()`](#functions:yaml_decode) [`pack()`](#functions:pack) [`unpack()`](#functions:unpack) [`Iconv`](#functions:Iconv)
* **File and HTTP** [`File`](#functions:File) [`http()`](#functions:http) [`url_parse()`](#functions:url_parse)
* **Mail** [`envelope_address_parse()`](#functions:envelope_address_parse) [`envelope_localpart_escape()`](#functions:envelope_localpart_escape) [`header_addresslist_compile()`](#functions:header_addresslist_compile) [`header_addresslist_extract()`](#functions:header_addresslist_extract) [`header_dkim_decode()`](#functions:header_dkim_decode) [`xtext_encode()`](#functions:xtext_encode) [`xtext_decode()`](#functions:xtext_decode) [`spf_query()`](#functions:spf_query)
* **Mathematical** [`abs()`](#functions:abs) [`ceil()`](#functions:ceil) [`floor()`](#functions:floor) [`log()`](#functions:log) [`pow()`](#functions:pow) [`round()`](#functions:round) [`sqrt()`](#functions:sqrt)
* **MIME** [`MIME`](#functions:MIME) [`MailMessage`](#functions:_CPPv411MailMessage) [`MIMEPart`](#functions:_CPPv48MIMEPart)
* **Misc** [`gethostname()`](#functions:gethostname) [`uuid()`](#functions:uuid) [`uuidv5()`](#functions:uuidv5) [`syslog()`](#functions:syslog) [`inet_includes()`](#functions:inet_includes) [`inet_ntop()`](#functions:inet_ntop) [`inet_pton()`](#functions:inet_pton) [`inet_reverse()`](#functions:inet_reverse) [`Exception`](#functions:_CPPv49Exception)
* **Protocols** [`smtp_lookup_rcpt()`](#functions:smtp_lookup_rcpt) [`smtp_lookup_auth()`](#functions:smtp_lookup_auth)
* **String** [`chr()`](#functions:chr) [`ord()`](#functions:ord) [`str_repeat()`](#functions:str_repeat) [`str_replace()`](#functions:str_replace) [`str_find()`](#functions:str_find) [`str_rfind()`](#functions:str_rfind) [`str_lower()`](#functions:str_lower) [`str_upper()`](#functions:str_upper) [`str_slice()`](#functions:str_slice) [`str_split()`](#functions:str_split) [`str_strip()`](#functions:str_strip) [`str_rstrip()`](#functions:str_rstrip) [`str_lstrip()`](#functions:str_lstrip) [`str_chunk()`](#functions:str_chunk)
* **Regular expression** [`pcre_match()`](#functions:pcre_match) [`pcre_match_all()`](#functions:pcre_match_all) [`pcre_quote()`](#functions:pcre_quote) [`pcre_compile()`](#functions:pcre_compile) [`pcre_replace()`](#functions:pcre_replace)
* **Socket** [`Socket`](#functions:Socket) [`TLSSocket`](#functions:TLSSocket) [`X509`](#functions:X509) [`is_x509_resource()`](#functions:is_x509_resource) [`X509CRL`](#functions:X509CRL) [`is_x509crl_resource()`](#functions:is_x509crl_resource)
* **FFI** [`FFI`](#functions:FFI) [`FFIFunction()`](#functions:FFIFunction) [`FFIType`](#functions:FFIType) [`FFIValue`](#functions:FFIValue)
* **Shared memory** [`memory_add()`](#functions:memory_add) [`memory_dec()`](#functions:memory_dec) [`memory_delete()`](#functions:memory_delete) [`memory_exists()`](#functions:memory_exists) [`memory_fetch()`](#functions:memory_fetch) [`memory_inc()`](#functions:memory_inc) [`memory_store()`](#functions:memory_store) [`memory_update()`](#functions:memory_update)
* **Server** [`server_concurrency()`](#functions:server_concurrency)
* **Queue** [`queue_policy()`](#functions:queue_policy) [`queue_policy_delete()`](#functions:queue_policy_delete) [`queue_suspend()`](#functions:queue_suspend) [`queue_quota()`](#functions:queue_quota)
* **Metrics** [`metrics_increment()`](#functions:metrics_increment) [`metrics_decrement()`](#functions:metrics_decrement) [`metrics_set()`](#functions:metrics_set) [`metrics_get()`](#functions:metrics_get)
## Array
### array_combine(keys, values[, default_value])
Create a new array with the keys and values combines into a new array. Duplicate keys are updated.
* **Parameters:**
* **keys** (*array*) – the array of keys
* **values** (*array*) – the array of values
* **default_value** (*any*) – the default value if not enough values for all keys
* **Returns:**
an array
* **Return type:**
array
```hsl
array_combine(["a", "b", "c"], [1, 2], 0); // ["a"=>1,"b"=>2,"c"=>0]
array_combine(["a", "b", "a", "c"], [1, 2, 3, 4]); // ["a"=>3,"b"=>2,"c"=>4]
```
### array_every(callback, array)
Returns true if all items in the array are true based on the result of the callback function.
* **Parameters:**
* **callback** (*function*) – the callback
* **array** (*array*) – the array
* **Returns:**
true if all callbacks return true
* **Return type:**
boolean
The callback function should take one argument (value) and return a boolean value.
```hsl
array_every(function ($x) { return $x % 2 == 0; }, [0, 2, 4, 6]); // true
array_every(function ($x) { return $x % 2 == 0; }, [0, 2, 5, 6]); // false
```
### array_filter(callback, array[, options])
Returns the filtered items from the array using a callback.
* **Parameters:**
* **callback** (*function*) – the callback
* **array** (*array*) – the array
* **options** (*array*) – options array
* **Returns:**
array of filtered values, keys are preserved
* **Return type:**
array
The following options are available in the options array.
> * **keys** (boolean) Find values based on keys. The default is `false`.
The callback function should take one argument (value) and return a boolean value.
```hsl
array_filter(function ($x) { return $x % 2 == 0; }, [0, 1, 2, 3]); // even values
array_filter(is_number, [0, "Hello World", 2]);
```
### array_find(callback, array[, options])
Return the first element that matches in the array. If no element is found `none` is returned.
* **Parameters:**
* **callback** (*function*) – the callback
* **array** (*array*) – the array
* **options** (*array*) – options array
* **Returns:**
the value if found
* **Return type:**
any
The following options are available in the options array.
> * **keys** (boolean) Find values based on keys. The default is `false`.
The callback function should take one argument (value) and return a boolean value.
```hsl
array_find(function ($x) { return $x["id"] === 2; }, [["id" => 1, "name" => "a"], ["id" => 2, "name" => "b"]]); // ["id"=>2,"name"=>"b"]
```
### array_includes(needle, array)
Returns true if needle is found in the array.
* **Parameters:**
* **needle** (*any*) – the value to match or a callback function
* **array** (*array*) – the array
* **Returns:**
true if needle is found
* **Return type:**
boolean
The callback function should take one argument (value) and return a boolean value. If the needle is not a function, it will be matched using the strict comparison operator (`===`).
```hsl
array_includes(function ($x) { return $x === 2; }, [0, 1, 2, 3]); // true
array_includes(false, [0, none, ""]); // false
```
### array_join(array[, separator])
Join the elements in the array with a separator returning a string
* **Parameters:**
* **array** (*array*) – the array
* **separator** (*string*) – the separator
* **Returns:**
a string from an array
* **Return type:**
string
> **See also**
>
> To split a string to an array, see [`str_split()`](#functions:str_split).
### array_keys(array)
Returns the keys in the array.
* **Parameters:**
**array** (*array*) – the array
* **Returns:**
array’s keys
* **Return type:**
array
### array_map(callback, array)
Returns values from the array with the callback applied.
* **Parameters:**
* **callback** (*function*) – the callback
* **array** (*array*) – the array
* **Returns:**
array of values, keys are preserved
* **Return type:**
array
The function should take one argument (value) and return a value.
```hsl
array_map(function ($x) { return $x * 2; }, [0, 1, 2, 3]); // double values
```
### array_random(array[, seed])
Returns a random key and value from the array. If the array is empty *none* is returned.
* **Parameters:**
* **array** (*array*) – the array
* **seed** (*number*) – a seed used to make the result deterministic
* **Returns:**
array with key and value
* **Return type:**
array
The function should take one argument (value) and return a value.
```hsl
array_random(["hello" => "foo", "world" => "bar"]); // ["world", "bar"] // [key, value]
```
### array_range(start, stop[, step = 1])
Returns an array from a numeric range (half-open) with the given steps.
* **Parameters:**
* **start** (*number*) – the first number
* **stop** (*number*) – the last number (not included)
* **step** (*number*) – the step between numbers
* **Returns:**
an array with numbers
* **Return type:**
array
```hsl
foreach (array_range(0, 9) as $i) // 0,1,2,..,8
echo $i;
```
### array_reduce(callback, array[, initial])
Reduces the values in the array using the callback from left-to-right, optionally starting with a initial value.
* **Parameters:**
* **callback** (*function*) – the callback
* **array** (*array*) – the array
* **initial** (*any*) – the initial value
* **Returns:**
a single value
* **Return type:**
any
The function should take two arguments (carry and value) and return a value.
If no initial value is provided and;
> * the array is empty, an error will be raised.
> * the array contains one value, that value will be returned.
```hsl
array_reduce(function ($carry, $x) { return $carry + $x; }, [0, 1, 2, 3]); // sum values
```
### array_reverse(array)
Return array in reverse order. Numeric keys are reindexed.
* **Parameters:**
**array** (*array*) – the array
* **Returns:**
array in reverse order
* **Return type:**
array
### array_sort(callback, array[, options])
Returns the array sorted (with index association maintained) using the callback function to determine the order. The sort is not guaranteed to be stable.
* **Parameters:**
* **callback** (*function*) – the callback
* **array** (*array*) – the array
* **options** (*array*) – options array
* **Returns:**
a sorted array
* **Return type:**
array
The following options are available in the options array.
> * **keys** (boolean) Sort the array based on their keys. The default is `false`.
The callback function should take two arguments (a and b) and return true if a is less-than b.
```hsl
array_sort(function ($a, $b) { return $a < $b; }, [2, 3, 1]); // sort
array_sort(function ($a, $b) { return $a > $b; }, [2, 3, 1]); // reverse-sort
```
> **Note**
>
> Some other languages (eg. javascript and PHP) use a trivalue function (-1, 0, 1) in a similar way in order to determine the order. HSL does not since if needed, a trivalue function may be simulated internally using the provided less-than function. Further some sorting implementation may only need the less-than result hence the greater-than and equality result may be superfluous to establish.
>
> > ```hsl
> > function trivalue($a, $b, $lessthan)
> > {
> > if ($lessthan($a, $b)) return -1;
> > if ($lessthan($b, $a)) return 1;
> > return 0;
> > }
> > ```
### array_shuffle(array[, seed])
Return array in shuffled order. Numeric keys are reindexed.
* **Parameters:**
* **array** (*array*) – the array
* **seed** (*number*) – a seed used to make the result deterministic
* **Returns:**
array in shuffled order
* **Return type:**
array
```hsl
$array = array_shuffle($array, hash("hello world"));
```
### array_unique(array)
Return an array with unique values (the first key is preserved).
* **Parameters:**
**array** (*array*) – the array
* **Returns:**
array with unique values
* **Return type:**
array
## Cryptographic
### aes_decrypt(message, key, mode[, options])
Decrypt a message using AES. On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to decrypt
* **key** (*string*) – the key as raw bytes (no padding is done)
* **mode** (*string*) – the block cipher mode of operation (`ecb` or `cbc`)
* **options** (*array*) – options array
* **Returns:**
the message decrypted
* **Return type:**
string or none
The following options are available in the options array.
> * **iv** (string) The initialization vector as bytes (16 bytes for `cbc`).
> * **padding** (boolean) Use PKCS7 padding. The default is `true`.
> **Note**
>
> The key length must be either 16 bytes for AES-128, 24 bytes for AES-192 or 32 bytes for AES-256. No NUL bytes padding nor truncation is done on either the key or iv. The example below shows how to do manual padding.
>
> ```hsl
> $message = aes_decrypt(
> $encrypted,
> pack("a32", "short aes-256 key"),
> "cbc",
> ["iv" => pack("x16")]
> );
> ```
### aes_encrypt(message, key, mode[, options])
Encrypt a message using AES. On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to encrypt
* **key** (*string*) – the key as raw bytes (no padding is done)
* **mode** (*string*) – the block cipher mode of operation (`ecb` or `cbc`)
* **options** (*array*) – options array
* **Returns:**
the message encrypted
* **Return type:**
string or none
The following options are available in the options array.
> * **iv** (string) The initialization vector as bytes (16 bytes for `cbc`).
> * **padding** (boolean) Use PKCS7 padding. The default is `true`.
> **Note**
>
> The key length must be either 16 bytes for AES-128, 24 bytes for AES-192 or 32 bytes for AES-256. No NUL bytes padding nor truncation is done on either the key or iv. The example below shows how to do manual padding.
>
> ```hsl
> $encrypted = aes_encrypt(
> $message,
> pack("a32", "short aes-256 key"),
> "cbc",
> ["iv" => pack("x16")]
> );
> ```
### hmac_md5(key, message[, options])
Return the HMAC MD5 hash of message with the key.
* **Parameters:**
* **key** (*string*) – the HMAC key
* **message** (*string*) – the value to hash
* **options** (*array*) – options array
* **Returns:**
the hash value hex encoded
* **Return type:**
string
The following options are available in the options array.
> * **binary** (boolean) Return binary instead of hex format. The default is `false`.
### hmac_sha1(key, message[, options])
Return the HMAC SHA1 hash of message with the key.
* **Parameters:**
* **key** (*string*) – the HMAC key
* **message** (*string*) – the value to hash
* **options** (*array*) – options array
* **Returns:**
the hash value hex encoded
* **Return type:**
string
The following options are available in the options array.
> * **binary** (boolean) Return binary instead of hex format. The default is `false`.
### hmac_sha2(key, message, hashsize[, options])
Return the HMAC SHA2 hash of message with the key.
* **Parameters:**
* **key** (*string*) – the HMAC key
* **message** (*string*) – the value to hash
* **hashsize** (*number*) – the hash size (must be 256 or 512)
* **options** (*array*) – options array
* **Returns:**
the hash value hex encoded
* **Return type:**
string
The following options are available in the options array.
> * **binary** (boolean) Return binary instead of hex format. The default is `false`.
### md5(message[, options])
Return the MD5 hash of message.
* **Parameters:**
* **message** (*string*) – the value to hash
* **options** (*array*) – options array
* **Returns:**
the hash value hex encoded
* **Return type:**
string
The following options are available in the options array.
> * **binary** (boolean) Return binary instead of hex format. The default is `false`.
### sha1(message[, options])
Return the SHA1 hash of message.
* **Parameters:**
* **message** (*string*) – the value to hash
* **options** (*array*) – options array
* **Returns:**
the hash value hex encoded
* **Return type:**
string
The following options are available in the options array.
> * **binary** (boolean) Return binary instead of hex format. The default is `false`.
### sha2(message, hashsize[, options])
Return the SHA2 hash of message.
* **Parameters:**
* **message** (*string*) – the value to hash
* **hashsize** (*number*) – the hash size (must be 256 or 512)
* **options** (*array*) – options array
* **Returns:**
the hash value hex encoded
* **Return type:**
string
The following options are available in the options array.
> * **binary** (boolean) Return binary instead of hex format. The default is `false`.
### hash(message)
Return the numeric hash value of the message. The hash value is same for equal messages.
* **Parameters:**
**message** (*string*) – the value to hash
* **Returns:**
the hash value
* **Return type:**
number
### pkcs11_load(uri)
Return an array of fetched resources types of (PrivateKey’s, X509 and X509CRL). Only URI starting with *pkcs11:* are supported.
* **Parameters:**
**uri** (*string*) – the pkcs11 uri
* **Returns:**
resources
* **Return type:**
an array of resources
### rsa_privatekey(privatekey[, options])
Return a RSA privatekey resource from a PEM certificate (`--- BEGIN PRIVATE KEY ---`). On error `none` is returned.
* **Parameters:**
* **privatekey** (*string*) – the private key in PEM format
* **options** (*array*) – options array
* **Returns:**
the privatekey
* **Return type:**
RSAPrivateKey resource or none
The following options are available in the options array.
> * **password** (string) The password used to decrypt an encrypted key.
### is_rsa_privatekey(privatekey)
Return a true if privatekey is a RSA privatekey resource.
* **Parameters:**
**privatekey** (*any*) – the privatekey
* **Returns:**
if the argument is a privatekey
* **Return type:**
boolean
### rsa_sign(message, privatekey[, options])
RSA sign a message digest using a hash function. On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to sign
* **privatekey** (*RSAPrivateKey* *or* *string*) – the private key resource or string
* **options** (*array*) – options array
* **Returns:**
the message signature
* **Return type:**
string or none
The following options are available in the options array.
> * **hash** (string) The hash method to use (`md5`, `sha1`, `sha256` or `sha512`). The default is `sha256`.
> * **format** (string) The private key format to use `PrivateKeyInfo` (PKCS#8) or `RSAPrivateKey`. The default is `RSAPrivateKey`.
> * **pem** (boolean) If the private key is in PEM format or raw bytes. The default is `false`.
> * **id** (boolean) If the private key is given as an id of an RSA key in the configuration. The default is `false`.
### rsa_verify(message, signature, publickey[, options])
RSA verify a message digest using a hash function. On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to verify
* **signature** (*string*) – the signature for the message as raw bytes
* **publickey** (*string*) – the public key
* **options** (*array*) – options array
* **Returns:**
if the signature verifies
* **Return type:**
boolean or none
The following options are available in the options array.
> * **hash** (string) The hash method to use (`md5`, `sha1`, `sha256` or `sha512`). The default is `sha256`.
> * **format** (string) The public key format to use `SubjectPublicKeyInfo` or `RSAPublicKey`. The default is `RSAPublicKey`.
> * **pem** (boolean) If the public key is in PEM format or raw bytes. The default is `false`.
> * **id** (boolean) If the public key is given as an id of an RSA key in the configuration. The default is `false`.
### ec_privatekey(privatekey[, options])
Return a EC privatekey resource from a PEM certificate (`--- BEGIN PRIVATE KEY ---`). On error `none` is returned.
* **Parameters:**
* **privatekey** (*string*) – the private key in PEM format
* **options** (*array*) – options array
* **Returns:**
the privatekey
* **Return type:**
ECPrivateKey resource or none
The following options are available in the options array.
> * **password** (string) The password used to decrypt an encrypted key.
### is_ec_privatekey(privatekey)
Return a true if privatekey is a EC privatekey resource.
* **Parameters:**
**privatekey** (*any*) – the privatekey
* **Returns:**
if the argument is a privatekey
* **Return type:**
boolean
### ed25519_privatekey(privatekey[, options])
Return a ED25519 privatekey resource from a PEM certificate (`--- BEGIN PRIVATE KEY ---`). On error `none` is returned.
* **Parameters:**
* **privatekey** (*string*) – the private key in PEM format
* **options** (*array*) – options array
* **Returns:**
the privatekey
* **Return type:**
ED25519PrivateKey resource or none
The following options are available in the options array.
> * **password** (string) The password used to decrypt an encrypted key.
### is_ed25519_privatekey(privatekey)
Return a true if privatekey is a ED25519 privatekey resource.
* **Parameters:**
**privatekey** (*any*) – the privatekey
* **Returns:**
if the argument is a privatekey
* **Return type:**
boolean
### ed25519_sign(message, privatekey)
ED25519 sign a message. On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to sign
* **privatekey** (*ED25519PrivateKey* *or* *string*) – the private key resource or string (as raw bytes)
* **Returns:**
the message signature
* **Return type:**
string or none
### ed25519_verify(message, signature, publickey)
ED25519 verify a message. On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to verify
* **signature** (*string*) – the signature as raw bytes
* **publickey** (*string*) – the public key as raw bytes
* **Returns:**
if the signature verifies
* **Return type:**
boolean or none
### pkcs7_sign(message, certificate[, options])
PKCS7 sign (S/MIME) a message. If certificate is given as an associative array it should
include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*.
If certificate is given as a string it should either be an `id` or a PEM encoded string with a x509 certificate, a chain and a privatekey.
On error `none` is returned.
* **Parameters:**
* **message** (*string*) – the message to sign
* **certificate** (*array* *or* *string*) – the certificate and privatekey to use
* **options** (*array*) – options array
* **Returns:**
the message signature
* **Return type:**
string or none
The following options are available in the options array.
> * **id** (boolean) If a certificate argument is given as an string, use this to indicate a id of a certificate in the configuration. The default is `false`.
> * **detached** (boolean) If the signature should be detached (not include the message itself). The default is `true`.
If the certificate argument contains multiple certificates (intermediates) they will be included in the signature as well.
### cms_sign(message, certificate[, options])
CMS sign (S/MIME) a message. If certificate is given as an associative array it should
include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*.
If certificate is given as a string it should either be an `id` or a PEM encoded string with a x509 certificate, a chain and a privatekey.
It will return an associative array with an `result` (boolean). If false, an `error` (string) is set.
If true, data is returned the `data` (string).
* **Parameters:**
* **message** (*string*) – the message to sign
* **certificate** (*array* *or* *string*) – the certificate and privatekey to use
* **options** (*array*) – options array
* **Returns:**
the result
* **Return type:**
array
The following options are available in the options array.
> * **id** (boolean) If a certificate argument is given as an string, use this to indicate a id of a certificate in the configuration. The default is `false`.
> * **detached** (boolean) If the signature should be detached (not include the message itself). The default is `true`.
> * **noattr** (boolean) Control the CMS_NOATTR. The default is `false`.
> * **binary** (boolean) Control the CMS_BINARY. The default is `true`.
> * **nosmimecap** (boolean) Control the CMS_NOSMIMECAP. The default is `false`.
> * **usekeyid** (boolean) Control the CMS_USE_KEYID. The default is `false`.
> * **digest** (string) This can be `sha256` or `sha512`. The default is `sha256`.
If the certificate argument contains multiple certificates (intermediates) they will be included in the signature as well.
### cms_verify(signature, message, certificates[, options])
CMS verify (S/MIME) a message. The certificate should be given as an associative array and must
include an array of `certs` (*X509Resource*) and/or an array of `store` (*X509Resource*).
It will return an associative array with an `result` (boolean). If false, an `error` (string) is set and if possible an array of `certs` (X509Resource).
If true, an array of `signers` (X509Resource), an array of `certs` (X509Resource), if data is returned the `data` (string) and a `cms` classification
if the data is enclosed in another cms object (one of `data`, `signed`, `digest`, `compressed`, `encrypted`, `enveloped`, `unknown`).
* **Parameters:**
* **signature** (*string*) – the signature to verify
* **message** (*string* *or* *none*) – the message to verify (may be `none` for nondetached)
* **certificates** (*array*) – the certificates to use to verify
* **options** (*array*) – options array
* **Returns:**
the result
* **Return type:**
array
The following options are available in the options array.
> * **detached** (boolean) If the signature should be detached (not include the message itself). The default is `true`.
> * **data** (boolean) If the signature data should be return. The default is `true`.
> * **nointern** (boolean) When verifying a message normally certificates (if any) included in the message are searched for the signing certificate. The default is `false`.
### cms_encrypt(message, certificates[, options])
CMS encrypt (S/MIME) a message. The certificate should be given as an associative array and must
include an array of `certs` (*X509Resource*).
It will return an associative array with an `result` (boolean). If false, an `error` (string) is set.
If true, data is returned the `data` (string).
* **Parameters:**
* **message** (*string*) – the signature to encrypt
* **certificates** (*array*) – the certificate to use to encrypt the message
* **options** (*array*) – options array
* **Returns:**
the result
* **Return type:**
array
The following options are available in the options array.
> * **cipher** (string) This can be `des-ede3-cbc` or `aes-256-cbc`. The default is `des-ede3-cbc`.
> * **usekeyid** (boolean) Control the CMS_USE_KEYID. The default is `false`.
### cms_decrypt(message, certificate[, options])
CMS decrypt (S/MIME) a message. If certificate is given as an associative array it should
include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`).
If certificate is given as a string it should either be an `id` or a PEM encoded string with a x509 certificate and a privatekey.
It will return an associative array with an `result` (boolean). If false, an `error` (string) is set.
If true, data is returned the `data` (string) and a `cms` classification
if the data is enclosed in another cms object (one of `data`, `signed`, `digest`, `compressed`, `encrypted`, `enveloped`, `unknown`).
* **Parameters:**
* **message** (*string*) – the message to decrypt
* **certificate** (*array*) – the certificate and privatekey to use
* **options** (*array*) – options array
* **Returns:**
the result
* **Return type:**
array
The following options are available in the options array.
> * **id** (boolean) If a certificate argument is given as an string, use this to indicate a id of a certificate in the configuration. The default is `false`.
### cms_type(message)
Get the CMS type of a message, eg. if it is `pkcs7-signedData` (signed) or `pkcs7-envelopedData` (encrypted) as return by OpenSSL `CMS_get0_type()` encoded with `OBJ_obj2txt()`.
* **Parameters:**
**message** (*string*) – the message to check
* **Returns:**
the message type
* **Return type:**
string or none
### cms_recipients(message)
Get recipients information from a S/MIME encrypted message. This can be used to determine which certificate to use for decryption.
It will return an associative array with either `keyid` (string) or `serial_number` (string) and `issuer` (array).
On error `none` is returned.
* **Parameters:**
**message** (*string*) – the message to get recipients information from
* **Returns:**
the recipients information
* **Return type:**
array or none
### random_bytes(bytes)
Return a string of random bytes (at most 1MiB).
* **Parameters:**
**bytes** (*number*) – number of bytes to return
* **Returns:**
random bytes
* **Return type:**
string
### random_number([first, last[, seed]])
Return a random integer between first and last (inclusive) or a random double (decimal) between 0 and 1 (inclusive).
* **Parameters:**
* **first** (*number*) – first possible number
* **last** (*number*) – last possible number
* **seed** (*number*) – a seed used to make the result deterministic
* **Returns:**
the random number
* **Return type:**
number
### crypt(key, salt)
Uses the underlying operating system’s `crypt()` function.
* **Parameters:**
* **key** (*string*) – the user’s typed password
* **salt** (*string*) – the salt
* **Returns:**
the encrypted string
* **Return type:**
string
```hsl
if (crypt($password, $encryptedpassword) === $encryptedpassword)
echo "match";
```
## Data types
### length(value)
Return the length of an array (items) or a string (characters). For all other datatypes *none* is returned.
* **Parameters:**
**value** (*any*) – the value
* **Returns:**
the length
* **Return type:**
number or none
### array([...args])
This function creates an array.
* **Parameters:**
**...****args** (*any*) – the input
* **Returns:**
an array
* **Return type:**
array
> **Note**
>
> *array* is not a function, it’s a language construct to create an [array](#literals:arraytype) type. It’s an alias for the short array syntax `[]`.
### boolean(value)
This function converts the input of value to the boolean type (according to the [truthiness](#operators:truthtable)) table.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
a boolean
* **Return type:**
boolean
### number(value)
This function converts the input of value to the number type. Decimal and hexadecimal (*Ox*) numbers are supported. If the input contains an invalid number as string or type `0` is returned.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
a number
* **Return type:**
number
### string(value)
This function converts the input of value to the string type, hence converting it to its string representation.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
a string
* **Return type:**
string
### is_array(value)
Returns true if the type of value is an array.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
the result
* **Return type:**
boolean
### is_boolean(value)
Returns true if the type of value is a boolean.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
the result
* **Return type:**
boolean
### is_function(value)
Returns true if the type of value is a function.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
the result
* **Return type:**
boolean
### is_number(value)
Returns true if the type of value is a number.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
the result
* **Return type:**
boolean
### is_object(value)
Returns true if the type of value is an object.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
the result
* **Return type:**
boolean
### is_string(value)
Returns true if the type of value is a string.
* **Parameters:**
**value** (*any*) – the input
* **Returns:**
the result
* **Return type:**
boolean
### isset(x)
Returns true if the variable is defined.
> > **Note**
> >
> > This is not a regular function. It’s a language construct and will only accept variables as input.
* **Parameters:**
**x** (*variable*) – a variable
* **Returns:**
the result
* **Return type:**
boolean
### unset(x)
Unsets the variable or array index or slice of x, it return true if the variable or array index was defined.
> > **Note**
> >
> > This is not a regular function. It’s a language construct and will only accept variables as input.
* **Parameters:**
**x** (*variable*) – a variable
* **Returns:**
if x was unset
* **Return type:**
boolean
### *class* Map
The Map class maps keys to values. Keys are unique and may only point to a single value.
This map is strongly-typed, meaning you have to define which data types you intend to store in it.
There is a limited number of key types, but they are optimized for performance.
As for value you may choose to use the *any* type to support all data types and also mixed in the map,
but it is more memory efficent to use a specific type.
#### constructor(keytype, valuetype)
Create a Map with the following data types.
* **Parameters:**
* **keytype** (*string*) – the key type
* **valuetype** (*string*) – the value type
The following key types are available.
* `string`
* `uint32`, `sint32`
The following value types are available.
* `string`
* `uint32`, `sint32`
* `double`
* `boolean`
* `any` (holds any value)
```hsl
$map = Map("string", "uint32");
$map->set("foo", 123);
```
> **Warning**
>
> If the `Map` is shared between concurrent script executions (eg. using `import` or `cache`) do *not* modify (`set`, `delete` et.c.) the object as those operations are not thread-safe.
#### set(key, value)
Add or update the key with the value.
* **Parameters:**
* **key** (*string*) – the key
* **value** (*string*) – the value
* **Returns:**
if the element was created (true) or overwritten (false)
* **Return type:**
boolean
#### has(key)
Check if the key exists.
* **Parameters:**
**key** (*string*) – the key
* **Returns:**
if the key exists
* **Return type:**
boolean
#### get(key)
Get the value of key or `none` if it doesn’t exists.
* **Parameters:**
**key** (*string*) – the key
* **Returns:**
the value of key or `none`
* **Return type:**
any
#### delete(key)
Delete the key from the map.
* **Parameters:**
**key** (*string*) – the key
* **Returns:**
if the key existed before the remove
* **Return type:**
boolean
#### merge(key, value)
Merge key values in-place using the (+ operator). This can be used to add items to arrays or increment numbers.
* **Parameters:**
* **key** (*string*) – the key
* **value** (*string*) – the value
* **Returns:**
if the key existed before the merge
* **Return type:**
boolean
#### size()
Get number of keys in the map.
* **Returns:**
keys in map
* **Return type:**
number
#### clear()
Delete all keys in the map.
* **Return type:**
none
#### toArray()
Convert the Map into an array.
* **Returns:**
the values as an array
* **Return type:**
array
#### entries()
Iterator to all keys and values in the map in no specific order.
The iterators value will be an array containg the `[0=>key, 1=>value]`.
If iterating the map with foreach, the key and value will be mapped accordingly.
* **Return type:**
[`Iterator`](#literals:Iterator)
```hsl
$map = Map("string", "uint32");
$map->set("foo", 123);
$map->set("bar", 456);
$it = $map->entries();
echo $it->next(); // ["done"=>false,"value"=>[0=>"bar",1=>456]]
echo $it->next(); // ["done"=>false,"value"=>[0=>"foo",1=>123]]
echo $it->next(); // ["done"=>true,"value"=>]
foreach ($map->entries() as $k => $v)
echo "$k = $v";
// bar = 456
// foo = 123
```
### *class* Set
The Set class holds keys in a unique collection.
This set is strongly-typed, meaning you have to define which data types you intend to store in it.
There is a limited number of key types, but they are optimized for performance.
#### constructor(keytype)
Create a Set with the following data type.
* **Parameters:**
**keytype** (*string*) – the key type
The following key types are available.
* `string`
* `uint32`, `sint32`
```hsl
$set = Set("string");
$set->add("foo");
```
> **Warning**
>
> If the `Set` is shared between concurrent script executions (eg. using `import` or `cache`) do *not* modify (`add`, `delete` et.c.) that object as those operations are not thread-safe.
#### add(key)
Add the key to the set.
* **Parameters:**
**key** (*string*) – the key
* **Returns:**
if the key was added (true) or not (false)
* **Return type:**
boolean
#### has(key)
Check if the key exists.
* **Parameters:**
**key** (*string*) – the key
* **Returns:**
if the key exists
* **Return type:**
boolean
#### delete(key)
Delete the key from the set.
* **Parameters:**
**key** (*string*) – the key
* **Returns:**
if the key existed before the remove
* **Return type:**
boolean
#### size()
Get number of keys in the set.
* **Returns:**
keys in set
* **Return type:**
number
#### clear()
Delete all keys in the set.
* **Return type:**
none
#### toArray([options])
Convert the Set into an array.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
the values as an array
* **Return type:**
array
The following options are available in the options array.
* **keys** (boolean) Set values as keys. The default is `false`.
#### values()
Iterator to all keys in the set in no specific order.
If iterating the set with foreach, the key (in the set) will be mapped to the value in the foreach statement.
* **Return type:**
[`Iterator`](#literals:Iterator)
```hsl
$set = Set("string");
$set->add("foo");
$set->add("bar");
$it = $set->values();
echo $it->next(); // ["done"=>false,"value"=>"bar"]
echo $it->next(); // ["done"=>false,"value"=>"foo"]
echo $it->next(); // ["done"=>true,"value"=>]
foreach ($set->values() as $v)
echo "$v";
// bar
// foo
```
## Date and time
### executiontime()
Return the elapsed time since the beginning of the code execution.
* **Returns:**
the time in seconds (with decimals)
* **Return type:**
number
### sleep(seconds)
Pause the code execution for x seconds.
* **Parameters:**
**seconds** (*number*) – the number of seconds to sleep
* **Returns:**
the time slept in seconds (with decimals)
* **Return type:**
number
> **Note**
>
> This function will suspend the current script execution while sleeping allowing for cooperative multitasking.
### strftime(format[, time][, options])
Format according to the [strftime](https://man7.org/linux/man-pages/man3/strftime.3.html) manual.
* **Parameters:**
* **format** (*string*) – the format string
* **time** (*number*) – the default is current time without timezone
* **options** (*array*) – options array
* **Returns:**
the time formatted (max length 100)
* **Return type:**
string
The following options are available in the options array.
* **local** (boolean) Expect the time to be in the current local timezone. The default is `true`.
```hsl
echo strftime("%H:%M:%S"); // prints current time eg "13:58:38"
```
### strptime(datestring, format[, options])
Parse a date string according to the [strptime](https://man7.org/linux/man-pages/man3/strptime.3.html) manual with the time without timezone.
* **Parameters:**
* **datestring** (*string*) – the date string
* **format** (*string*) – the format string
* **options** (*array*) – options array
* **Returns:**
the time in seconds
* **Return type:**
number
The following options are available in the options array.
* **local** (boolean) Expect the time to be in the current local timezone. The default is `true`.
```hsl
echo strptime("13:58:38", "%H:%M:%S"); // prints time of today at "13:58:38"
```
### strpduration(during)
Parse a string formated as a duration. Supported time periods are *d* (days) *h* (hours), *m* (minutes) and *s* (seconds).
On error `none` is returned.
* **Parameters:**
**during** (*string*) – the during string
* **Returns:**
the time in seconds
* **Return type:**
number
```hsl
echo strpduration("1h3s"); // 3603
echo strpduration("1d"); // 86400
echo strpduration("3600"); // 3600
```
### time()
Return elapsed seconds (unix time) since 1970-01-01T00:00:00Z without timezone.
* **Returns:**
the time in seconds (with decimals)
* **Return type:**
number
### timelocal()
Return elapsed seconds (unix time) since 1970-01-01T00:00:00Z with timezone.
* **Returns:**
the time in seconds (with decimals)
* **Return type:**
number
### uptime()
Return the monotonic time since system boot. Monotonic time is by definition suitable for relative time keeping, in contrast to [`time()`](#functions:time). If you want to obtain the script execution time use [`executiontime()`](#functions:executiontime).
* **Returns:**
the time in seconds (with decimals)
* **Return type:**
number
## DNS
### dns_query(host[, options])
Query for DNS records of a hostname.
* **Parameters:**
* **host** (*string*) – the host
* **options** (*array*) – options array
* **Returns:**
the result
* **Return type:**
array
The following options are available in the options array.
* **type** (string or number) Query type (one of `a`, `aaaa`, `mx`, `txt`, `cname`, `ns`, `ptr`, `srv`, `soa` or a RR type as number). The default is to query for `a` records.
* **extended_result** (boolean) Get an extended result with more RR information. Must be used with numeric RR types. The default is `false`.
An array with either `result` or `error` in set in an associative array. `dnssec` is always included. `result` is the list of results (addresses, hostnames or strings) and `error` is the string representation of *rcode* or *h_errno*.
If `extended_result` is `true`, more information will be available in the result array for each record.
| Type | Fields |
|---------|------------------------------------------------------------------------------------|
| `a` | `type`, `ttl`, `address` |
| `aaaa` | `type`, `ttl`, `address` |
| `mx` | `type`, `ttl`, `preference`, `exchange` |
| `txt` | `type`, `ttl`, `string` |
| `cname` | `type`, `ttl`, `target` |
| `ns` | `type`, `ttl`, `target` |
| `ptr` | `type`, `ttl`, `target` |
| `srv` | `type`, `ttl`, `priority`, `weight`, `port`, `target` |
| `soa` | `type`, `ttl`, `mname`, `rname`, `serial`, `refresh`, `retry`, `expire`, `minimum` |
| number | `type`, `ttl`, `data` |
```hsl
echo dns_query("nxdomain.halon.se");
// ["error"=>"NXDOMAIN","dnssec"=>false]
echo dns_query("halon.se");
// ["result"=>[0=>"54.152.237.238"],"dnssec"=>false]
echo dns_query(inet_reverse("8.8.8.8"), ["type" => "ptr", "extended_result" => true]);
// ["result"=>[0=>["type"=>"ptr","ttl"=>5,"target"=>"dns.google"]],"dnssec"=>false]
// DNSBL example
echo dns_query(inet_reverse("12.34.56.78", "dnsbl.example.com"));
// ["result"=>[0=>"127.0.0.1"],"dnssec"=>false]
// TLSA example
echo unpack("cccH*", dns_query("_25._tcp.mx.halon.io", ["type" => 52, "extended_result" => true])["result"][0]["data"]);
// [0=>3,1=>1,2=>1,3=>"42568063726264388f17d494cd3d01079ba1df4e60b1448e5670544cd7739218"]
```
> **Note**
>
> This function will suspend the current script execution while waiting for DNS responses allowing for cooperative multitasking.
### domain_includes(subdomain, domain[, options])
Test if subdomain is a subdomain of domain. If the domain starts with a dot `.` it must be a subdomain of domain, hence it will **not** even if *subdomain == domain*.
* **Parameters:**
* **subdomain** (*string*) – the subdomain
* **domain** (*string*) – the domain
* **options** (*array*) – options array
* **Returns:**
if subdomain is a subdomain of domain
* **Return type:**
boolean
The following options are available in the options array.
* **depth** (number) The maximum allowed depth of the subdomains. The default is no limit.
```hsl
domain_includes("www.halon.io", "halon.io"); // true
domain_includes("halon.io", "halon.io"); // true
domain_includes("www.halon.io", ".halon.io"); // true
domain_includes("halon.io", ".halon.io"); // false
domain_includes("test.www.halon.io", ".halon.io", ["depth" => 1]); // false
```
### domain_publicsuffix(domain)
Return the public suffix ([https://publicsuffix.org/](https://publicsuffix.org/)) of a domain name. If not a valid TLD (none is returned).
* **Parameters:**
**domain** (*string*) – the domain
* **Returns:**
the public suffix
* **Return type:**
string or none
```hsl
domain_publicsuffix("www.halon.io"); // "io"
domain_publicsuffix("www.google.co.uk"); // "co.uk"
domain_publicsuffix("example.badtld"); // none
```
### idna_encode(domain)
IDNA encode a domain (to punycode). On error `none` is returned.
* **Parameters:**
**domain** (*string*) – a unicode domain
* **Returns:**
the punycode (ASCII) domain
* **Return type:**
string or none
```hsl
echo idna_encode("fußball.example"); // xn--fuball-cta.example
```
### idna_decode(domain)
IDNA decode a domain (to unicode). On error `none` is returned.
* **Parameters:**
**domain** (*string*) – a punycode (ASCII) domain
* **Returns:**
the unicode domain
* **Return type:**
string or none
```hsl
echo idna_decode("xn--fuball-cta.example"); // fußball.example
```
## Encodings and JSON
### base32_encode(string[, options])
Base32 encode the string (RFC 4648).
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the base32 representation
* **Return type:**
string
The following options are available in the options array.
> * **padding** (boolean) Use padding. The default is `true`.
### base32_decode(string)
Base32 decode the string (RFC 4648).
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string representation
* **Return type:**
string
### base64_encode(string)
Base64 encode the string.
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the base64 representation
* **Return type:**
string
### base64_decode(string)
Base64 decode the string.
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string representation
* **Return type:**
string
### base64url_encode(string)
Base64 URL encode the string.
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the base64 url representation
* **Return type:**
string
### base64url_decode(string)
Base64 URL decode the string.
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string representation
* **Return type:**
string
### zlib_compress(string[, compression[, windowbits]])
Compress the string unsing zlib compression.
* **Parameters:**
* **string** (*string*) – the input string
* **compression** (*number*) – the compression level (default 9)
* **windowbits** (*number*) – the window bits (default 15)
* **Returns:**
the compressed string
* **Return type:**
string
### zlib_uncompress(string[, windowbits])
Decompress the string using zlib.
* **Parameters:**
* **string** (*string*) – the input string
* **windowbits** (*number*) – the window bits (default 15)
* **Returns:**
the uncompressed string
* **Return type:**
string
### csv_encode(values[, options])
Encode an array of strings as CSV encoded data. Multiline CSV data is supported.
* **Parameters:**
* **values** (*array*) – strings to encode
* **options** (*array*) – options array
* **Returns:**
the CSV representation
* **Return type:**
string or none
The following options are available in the options array.
> * **delimiter** (string) The format separator. The default is `,`.
> * **escape** (string) Additional escape character (causing double quotes). The default is `"\r\n` and the delimiter.
```hsl
echo csv_encode(["Hello", "World", "Hello World"], ["escape" => " "]);
// Hello,World,"Hello World"
```
### csv_decode(string[, options])
Parse CSV data as string. Multiline CSV data is supported. On error `none` is returned.
* **Parameters:**
* **string** (*string*) – CSV formated string
* **options** (*array*) – options array
* **Returns:**
an array of data
* **Return type:**
array or none
The following options are available in the options array.
> * **delimiter** (string) The format separator. The default is `,`.
> * **comment** (string) The comment character (first character only, eg `#` or `;`). The default is not to allow comments.
> * **header** (boolean) If the CSV data includes a header. The default is `true`.
> * **schema** (array) Use a schema to convert columns to types.
The schema should be of the format of being an array keyed on the column name.
```default
[
"columnname" => [
"type" => "string", "boolean" or "number",
"format" => "json" or "regex",
"nullable" => true or false or [ "", "NULL", ... ],
"true" => [ "True", ... ],
"false" => [ "False", ... ],
"min_length" => ...
...other validation methods....
],
...
]
```
If the column is nullable either set `nullable` to `true` (to treat empty strings as *none*) or set `nullable` to an array of values to treat as *none* (eg. `["NULL"]`). Likewise the boolean type has a `true` and `false` property for truthy and falsy values. The default is `["true"]` and `["false"]` (all lowercase).
Various types have different types of validations available.
| Type | Validate |
|----------|-------------------------------------|
| `string` | `min_length` (number) |
| `string` | `max_length` (number) |
| `string` | `pattern` (string) |
| `string` | `format` (string) `json` or `regex` |
| `number` | `minimum` (number) |
| `number` | `maximum` (number) |
| `number` | `exclusive_minimum` (number) |
| `number` | `exclusive_maximum` (number) |
| `number` | `multiple_of` (number) |
| *any* | `enum` (array) |
```hsl
echo csv_decode("enabled\nyes\nno", ["schema" => [
"enabled" => ["type" => "boolean", "true" => ["yes"], "false" => ["no"]]
]]);
// [0=>["enabled"=>true],1=>["enabled"=>false]]
```
> **Note**
>
> It’s possible to import CSV data at compile time to a variable using the [import](#structures:data-import) statement.
### json_encode(value[, options])
JSON encode a HSL data type. On error `none` is returned.
* **Parameters:**
* **value** (*any*) – HSL data type
* **options** (*array*) – options array
* **Returns:**
a JSON representation of value
* **Return type:**
string or none
The following options are available in the options array.
> * **ensure_ascii** (boolean) Convert all non-ASCII characters (UTF-8) to unicode (`\uXXXX`). The default is `true`.
> * **pretty_print** (boolean) Pretty print the JSON output. The default is `false`.
> * **type_error** (boolean) If conversion should fail on non-convertable types (or convert to `none`). The default is `true`.
Encode an array, objects (public members), number or string into a JSON representation (string). The encoding distinguishes arrays from objects if they are sequentially numbered from zero. On encoding errors an object with the data type of undefined is returned. All non-ASCII characters will be escaped as Unicode code points (\\uXXXX).
> **Note**
>
> Since object keys are converted to strings (even numeric once) a [`json_encode()`](#functions:json_encode) followed by a [`json_decode()`](#functions:json_decode) does not always yield the same result.
### json_decode(string[, options])
Decodes a JSON string into a HSL data type. On error `none` is returned.
* **Parameters:**
* **string** (*string*) – JSON serialized data
* **options** (*array*) – options array
* **Returns:**
the decoded string as the correct type
* **Return type:**
any or none
The following options are available in the options array.
> * **allow_comments** (boolean) Allow and ignore comments. The default is `false`.
> * **unicode_byte** (boolean) Non-standard decoding by interpret `\u00XX` as `\xXX`. The default is `false`.
> * **objects** (boolean) If JSON objects should imported as objects (instead of arrays). The default is `false`.
The following translations are done (JSON to HSL).
* **object** to **associative array** (is_array)
* **array** to **array** (is_array)
* **string** to **string** (is_string)
* **number** to **number** (is_number)
* **true** to `true` (is_boolean)
* **false** to `false` (is_boolean)
* **null** to **none**
> **Note**
>
> It’s possible to import JSON data at compile time to a variable using the [import](#structures:data-import) statement.
### url_encode(string)
URL encode the string (RFC 3986).
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the URL encoded representation
* **Return type:**
string
### url_decode(string)
URL decode the string (RFC 3986).
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string representation
* **Return type:**
string
### mimeword_encode(string)
MIME word encode the string (`=?utf-8?Q?string?=`).
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the MIME word encoded representation
* **Return type:**
string
### mimeword_decode(string)
MIME word decode the string (`=?charset?encoding?encoded-text?=`).
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string representation
* **Return type:**
string
### yaml_decode(string[, options])
Decodes a YAML string into a HSL data type. On error `none` is returned.
* **Parameters:**
* **string** (*string*) – YAML serialized data
* **options** (*array*) – options array
* **Returns:**
the decoded string as the correct type
* **Return type:**
any or none
The following options are available in the options array.
> * **tags** (array) An array of callback functions to parse tagged values. See example below.
Types are automatically detected based on the following rules:
> - Quoted strings are always converted to strings.
> - Unquoted values are first tested as numbers, then booleans, before falling back to strings.
```hsl
echo yaml_decode(''my-set: !Set
- item1
- item2
- item3
my-map: !Map
item1: !Regex /pattern1/
item2: !Regex /pattern2/'', [
"tags" => [
"!Map" => function ($data) {
$map = Map("string", "any");
foreach ($data as $k => $v)
$map->set($k, $v);
return $map;
},
"!Set" => function ($data) {
$map = Set("string");
foreach ($data as $k => $v)
$map->add($v);
return $map;
},
"!Regex" => function ($data) {
return pcre_compile($data);
},
"!PrivateKey" => function ($data) {
$k = rsa_privatekey($data);
if ($k == none)
$k = ed25519_privatekey($data);
return $k;
},
"!X509" => function ($data) {
return X509::String($data);
},
"!MailMessage" => function ($data) {
return MailMessage::String($data);
},
]
]); // ["my-set"=>__OBJECT#Set#1__,"my-map"=>__OBJECT#Map#1__]
```
> **Note**
>
> It’s possible to import YAML data at compile time to a variable using the [import](#structures:data-import) statement.
### pack(format[, ...args])
Pack arguments into a binary string. On error `none` is returned.
* **Parameters:**
* **format** (*string*) – the pack format
* **...****args** (*any*) – the arguments for the pack format
* **Returns:**
the packed data
* **Return type:**
string or none
The format may contain the following types. Some types may be followed by a `*` (an end-of-argument(s) repeater or a numeric repeater, eg. `"Z*C3"`).
| Code | Repeaters | Type | HSL type | Bytes |
|--------|-------------|-----------------------------|------------|---------|
| `a` | *n*, `*` | String | String | 1 |
| `C` | *n*, `*` | Char | Number | 1 |
| `e` | *n*, `*` | Double (LE) | Number | 8 |
| `E` | *n*, `*` | Double (BE) | Number | 8 |
| `H` | *n*, `*` | Hex | String | 1 |
| `n` | *n*, `*` | Unsigned short (16 bit, BE) | Number | 2 |
| `N` | *n*, `*` | Unsigned long (32 bit, BE) | Number | 4 |
| `v` | *n*, `*` | Unsigned short (16 bit, LE) | Number | 2 |
| `V` | *n*, `*` | Unsigned long (32 bit, LE) | Number | 4 |
| `x` | *n* | NULL | | 1 |
| `Z` | *n*, `*` | String (NULL terminated) | String | 1 |
### unpack(format, data[, offset = 0])
Unpack data from a binary string. On error `none` is returned.
* **Parameters:**
* **format** (*string*) – the unpack format
* **data** (*string*) – the packed data
* **offset** (*number*) – the offset to begin unpack from
* **Returns:**
the unpacked data
* **Return type:**
array or none
The format may contain the following types. Some types may be followed by a `*` (an end-of-argument(s) repeater or a numeric repeater, eg. `"Z*C3"`).
| Code | Repeaters | Type | HSL type | Bytes |
|--------|-------------|-----------------------------|------------|---------|
| `a` | *n*, `*` | String | String | 1 |
| `c` | *n*, `*` | Signed char | Number | 1 |
| `C` | *n*, `*` | Char | Number | 1 |
| `e` | *n*, `*` | Double (LE) | Number | 8 |
| `E` | *n*, `*` | Double (BE) | Number | 8 |
| `H` | *n*, `*` | Hex | String | 1 |
| `n` | *n*, `*` | Unsigned short (16 bit, BE) | Number | 2 |
| `N` | *n*, `*` | Unsigned long (32 bit, BE) | Number | 4 |
| `v` | *n*, `*` | Unsigned short (16 bit, LE) | Number | 2 |
| `V` | *n*, `*` | Unsigned long (32 bit, LE) | Number | 4 |
| `x` | *n* | Skip bytes | | 1 |
| `Z` | *n*, `*` | String (excluding NULL) | String | 1 |
### *class* Iconv
This class allows characters set conversions using the [Iconv](https://www.gnu.org/software/libiconv/) library. Be aware that different versions of Iconv may have subtile differences.
#### constructor(fromcharset, tocharset)
Create a class for conversions between two character sets.
* **Parameters:**
* **fromcharset** (*string*) – the input character set
* **tocharset** (*string*) – the output character set
```hsl
$text = Iconv("UTF-8", "ASCII//TRANSLIT//IGNORE");
echo $text->convert("Hello wörld"); // Hello wrld
```
#### close()
Close the Iconv resource and destroy the internal resource.
> **Note**
>
> Iconv classes are automatically garbage collected (closed). However you may want to explicitly call close.
#### convert(text)
Convert the text between the two different character sets. On error `none` is returned and you may call [`Iconv.errno()`](#functions:Iconv.errno) to get the error code.
* **Parameters:**
**text** (*string*) – text to convert
* **Returns:**
text
* **Return type:**
string or none
#### errno()
Get the latest errno returned from the underlying Iconv API.
* **Returns:**
errno
* **Return type:**
number
## File and HTTP
### *class* File
This class allows low level file access. A file resource is created for each File instance, this resource is automatically garbage collected (closed) once the object is destroyed.
#### constructor(filename[, options])
Open a virtual file from the configuration based on id, or a file on disk (using the `file://` syntax). On error `none` is returned.
* **Parameters:**
* **filename** (*string*) – the file name
* **options** (*array*) – options array
The following options are available in the options array.
* **buffered** (boolean) Buffer a `file://` into memory or read from file descriptor. The default is `true`.
```hsl
$file = File("myfile.txt");
while ($data = $file->read(8192))
echo $data;
```
> **Note**
>
> In *hsh* a special file URI is available for stdin `hsh://stdin`.
#### constructor(fileresource)
Open a virtual file from a FileResource. On error `none` is returned.
* **Parameters:**
**fileresource** (*FileResource*) – a FileResource
#### close()
Close the file and destroy the internal file resource.
* **Returns:**
none
* **Return type:**
None
> **Note**
>
> Files are automatically garbage collected (closed). However you may want to explicitly call close.
#### read([length])
Read data from file. On EOF an empty string is returned. On error `none` is returned.
* **Parameters:**
**length** (*number*) – bytes to read (max 65536)
* **Returns:**
data
* **Return type:**
string or none
> **Note**
>
> If no length is given, all the remaning data until EOF will be read in one operation.
#### readline()
Read a line from file (without the CRLF or LF). If there are no more lines `false` is returned, and on error `none` is returned.
* **Returns:**
data
* **Return type:**
string, boolean or none
#### seek(offset[, whence = "SEEK_SET"])
Seek to the offset in the file. On error `none` is returned.
* **Parameters:**
* **offset** (*number*) – the offset
* **whence** (*string*) – the position specified by whence
* **Returns:**
position
* **Return type:**
number or none
Whence may be any of
| Name | Position |
|----------|------------------------------------------|
| SEEK_CUR | relative offset to the current position |
| SEEK_SET | absolute offset from the beginning |
| SEEK_END | negative offset from the end of the file |
#### tell()
Get the current file position. On error `none` is returned.
* **Returns:**
position
* **Return type:**
number or none
#### getPath()
Get the path of a file. If no path information is available `none` is returned.
* **Returns:**
path
* **Return type:**
string or none
#### toFFIValue()
Return a shared `FILE*` pointer to a [C file stream](http://www.cplusplus.com/reference/cstdio/FILE/) representing the current File content and position. On error `none` is returned.
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue) or none
> **Note**
>
> The pointer is shared with the [`File`](#functions:File) instance, any operation on the [`File`](#functions:File) instance will also affect *FILE* pointer. However read operations on the *FILE* pointer will not affect the [`File`](#functions:File) position ([`File.tell()`](#functions:File.tell)). The *FILE* pointer must not be closed or deleted.
>
> ```c++
> #include
>
> extern "C" void processfile(FILE* fp) {
> fseek(fp, 0, SEEK_SET);
> }
> ```
#### *static* String(data)
Return a File resource containing the data.
* **Parameters:**
**data** (*string*) – the content
* **Returns:**
A file resource
* **Return type:**
[`File`](#functions:File)
```hsl
$file = File::String("Hello\nWorld");
echo $file->readline(); // "Hello"
```
#### *static* read(filename)
Read all data from file. On error `none` is returned.
* **Parameters:**
**filename** (*string*) – the file name
* **Returns:**
data
* **Return type:**
string or none
```hsl
$data = File::read("file.txt");
if ($data != none)
echo length($data);
```
### http(url[, options[, get[, post]]])
Make HTTP/HTTPS request to a URL and return the content.
* **Parameters:**
* **url** (*string*) – URL to request
* **options** (*array*) – options array
* **get** (*array*) – GET variables, replaced and encoded in URL as `$1`, `$2`, …
* **post** (*array* *,* *string* *or* *function*) – [POST data](#functions:postdata)
* **Returns:**
if the request was successful (2XX) the content is returned, otherwise `none` is returned
* **Return type:**
string, array or none
The following options are available in the options array.
> * **extended_result** (boolean) Get an extended result with response code. The default is `false`.
> * **connect_timeout** (number) Connection timeout (in seconds). The default is `10` seconds.
> * **timeout** (number) Timeout (in seconds) for the request. The default is to wait indefinitely.
> * **http_version** (string) Send the request using a specific HTTP version (`1.0`, `1.1` or `2`). The default is to use latest possible available.
> * **post_size** (number) Explicitly set the POST data size. The default is automatically calculated (but needs to be set when the POST data send using HTTP/1.0 and a POST data function callback).
> * **max_file_size** (number) Maximum file size (in bytes). The default is no limit.
> * **sourceip** (string) Explicitly bind an IP address. The default is to be chosen by the system.
> * **sourceipid** (string) Explicitly bind an IP address ID. The default is to be chosen by the system.
> * **unix_socket_path** (string) Unix socket path to connect to (instead of IP).
> * **method** (string) Request method. The default is `GET` unless `POST` data is sent.
> * **headers** (array) An array of additional HTTP headers as strings.
> * **response_headers** (boolean) Return the full request, including response headers (regardless of HTTP status). The default is `false`.
> * **redirects** (number) Specify the number of 304 redirects to follow (use `-1` for *unlimited*). The default is `0` (not to follow redirects).
> * **tls_verify_peer** (boolean) Verify peer certificate. The default is `true`.
> * **tls_verify_host** (boolean) Verify certificate hostname (CN). The default is `false`.
> * **tls_client_cert** (array or string) If given as an associative array it should include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. If given as a string use the following id of a certificate in the configuration as client certificate. The default is to not send a client certificate.
> * **proxy** (string) Use a HTTP proxy. See CURL_PROXY manual. The default is to inherit proxy settings from the system. Setting it to an empty string will disable the proxy.
> * **username** (string) The authenticate username (default is Basic authentication)
> * **password** (string) The authenticate password
> * **aws_sigv4** (string) aws_sigv4 option (see CURLOPT_AWS_SIGV4), for example `aws:amz:us-east-1:s3`. Use this option together with `username` and `password`.
If the option `extended_result` is `true`, the function returns an array containing the `status` code, `content`, and optionally an `headers` array if `response_headers` is enabled. If no valid HTTP response is received, `none` is returned.
The POST data may be sent in three different ways, depending on the different types, different encodings will be applied. The default *Content-Type* is always `application/x-www-form-urlencoded` if not changed using the `headers` options.
> - **Associative array**: The data will be properly encoded as `a=b&c=d` (x-www-form-urlencoded).
> - **String**: The data will be POSTed as is.
> - **Function callback**: The function callback is repeatedly called with one argument, the max length to return as a string. If the function return an empty string, the POST is done. If the function return `none` an error is triggered and the [`http()`](#functions:http) function will fail. This is useful for chunked POST data.
```hsl
$response = http("http://halon.io/", [
"extended_result" => true,
"headers" => ["Host: example.com", "Accept: application/json"]
]);
if ($response) {
echo $response;
}
$f = File::String(json_encode("POST this data as JSON"));
$response = http("http://halon.io/", [
"extended_result" => true,
"headers" => ["Content-Type: application/json", "Accept: application/json"]
], [], function ($max) closure ($f) {
return $f->read($max);
});
if ($response) {
echo $response;
}
$file = File::String("POST this data as form-data");
$name = "test.txt";
$boundary = uuid();
$preamble = array_join([
"--".$boundary,
"Content-Disposition: form-data; name=\"file\"; filename=\"".$name."\"",
"Content-Type: application/octet-stream",
], "\r\n")."\r\n\r\n";
$postamble = "\r\n--".$boundary."--\r\n";
$data = [$preamble, $file, $postamble];
$response = http("http://10.0.0.1/upload", [
"extended_result" => true,
"headers" => ["Content-type: multipart/form-data; boundary=".$boundary]
], [],
function ($size) closure ($data) {
while (length($data)) {
if (is_string($data[0])) {
$x = $data[0][0:$size];
$data[0] = $data[0][$size:];
} else {
$x = $data[0]->read($size);
}
if ($x !== "")
return $x;
$data = $data[1:];
}
return "";
});
if ($response) {
echo $response;
}
```
### url_parse(url[, options])
Parse an URL into its components (`scheme`, `username`, `password`, `hostname`, `port`, `path`, `query` and `fragment`). Missing components are not included.
* **Parameters:**
* **url** (*string*) – URL to parse
* **options** (*array*) – options array
* **Returns:**
if the parse was successful each components part is return as an associative array, otherwise `none` is returned
* **Return type:**
array or none
The following options are available in the options array.
> * **extended_result** (boolean) If `true` on error, an associative array with `error`, `errno` is returned. The default is `false`.
```hsl
echo url_parse("https://halon.io"); // ["scheme"=>"https","hostname"=>"halon.io","path"=>"/"]
```
## Mail
### envelope_address_parse(address)
Parse an email address into *localpart* and *domain*. On error `none` is returned.
* **Parameters:**
**address** (*string*) – email addresses
* **Returns:**
email address parts
* **Return type:**
array or none
```hsl
echo envelope_address_parse("user@example.com"); // ["localpart"=>"user","domain"=>"example.com"]
```
### envelope_localpart_escape(localpart)
Apply escaping to the an email envelope localpart.
* **Parameters:**
**localpart** (*string*) – localpart
* **Returns:**
escaped localpart
* **Return type:**
string
```hsl
echo envelope_localpart_escape("email address") . "@example.org"; // "email address"@example.org
```
### header_addresslist_compile(addresses)
Create a properly formated string of list of addresses often used with *From*, *To* and *CC* headers. The addresses array should include a list of associative arrays containg `name` and `address`.
* **Parameters:**
**addresses** (*array*) – an array of address objects
* **Returns:**
list of addresses
* **Return type:**
string
```hsl
echo header_addresslist_compile([
["name" => "John Doe", "address" => "john.doe@example.com"],
["name" => "Jane Doe", "address" => "jane.doe@example.com"],
]); // "John Doe" , "Jane Doe"
```
### header_addresslist_extract(value[, options])
Extract addresses from a header value or field, often used with *From*, *To* and *CC* headers. The *name* option will make this function return an associative array with the display `name` and `address`. On error `none` is returned.
* **Parameters:**
* **value** (*string*) – value to extract email addresses from
* **options** (*array*) – an options array
* **Returns:**
email addresses
* **Return type:**
array or none
The following options are available in the options array.
> * **name** (boolean) Return an array of *name* and *address*. The default is `false`.
> * **field** (boolean) If the value is a header field (Header: Value) format. The default is `false`.
> * **decode** (boolean) Decode MIME words (`=?charset?encoding?data?=`). The default is `true`.
```hsl
echo header_addresslist_extract("Charlie ; James ", ["name" => true]);
// [0=>["name"=>"Charlie","address"=>"charlie@example.org"],1=>["name"=>"James","address"=>"james@example.com"]]
$fromAddresses = header_addresslist_extract("Charlie ; James ");
if ($fromAddresses and length($fromAddresses) > 1)
echo "Too many From addresses";
```
### header_dkim_decode(value[, options])
Decode a Tag=Value list from a DKIM header value or field, often used with *DKIM-Signature* or *ARC-* headers. On error `none` is returned.
* **Parameters:**
* **value** (*string*) – value to extract tags from
* **options** (*array*) – an options array
* **Returns:**
tags
* **Return type:**
array or none
The following options are available in the options array.
> * **field** (boolean) If the value is a header field (Header: Value) format. The default is `false`.
```hsl
$tags = header_dkim_decode("v=1; d=domain; s=selector; h=to:from:date:subject");
if ($tags and isset($tags["s"]) and isset($tags["d"]))
echo $tags["s"]."._domainkey.".$tags["d"];
```
### xtext_encode(text)
Encode *xtext* according to the [rfc1891](https://tools.ietf.org/html/rfc1891).
* **Parameters:**
**text** (*string*) – value to encode
* **Returns:**
the encoded value
* **Return type:**
string
### xtext_decode(text)
Decode *xtext* according to the [rfc1891](https://tools.ietf.org/html/rfc1891).
* **Parameters:**
**text** (*string*) – value to decode
* **Returns:**
the decoded value
* **Return type:**
string
### spf_query(ip, helo, sender)
Check the SPF status of the sender.
* **Parameters:**
* **ip** (*string*) – IP or IPv6 address to check
* **helo** (*string*) – HELO/EHLO host name
* **sender** (*string*) – domain or email to lookup
* **Returns:**
the result
* **Return type:**
array
An array with a `result` (string) and in case of errors `error` (string) as an associative array. The `result` is returned as the string result as defined by libspf2 (eg. `pass`).
| libspf error | result |
|----------------------|-----------|
| SPF_RESULT_INVALID | invalid |
| SPF_RESULT_NEUTRAL | neutral |
| SPF_RESULT_PASS | pass |
| SPF_RESULT_FAIL | fail |
| SPF_RESULT_SOFTFAIL | softfail |
| SPF_RESULT_NONE | none |
| SPF_RESULT_TEMPERROR | temperror |
| SPF_RESULT_PERMERROR | permerror |
> **Note**
>
> This function will suspend the current script execution while waiting for DNS responses allowing for cooperative multitasking.
## Mathematical
### abs(number)
Return the absolute value of a number.
* **Parameters:**
**number** (*number*) – the numeric value to process
* **Returns:**
the absolute value
* **Return type:**
number
### ceil(number)
Return the integer value of a number by rounding up if necessary.
* **Parameters:**
**number** (*number*) – the numeric value to process
* **Returns:**
the integer value
* **Return type:**
number
### floor(number)
Return the integer value of a number by rounding down if necessary.
* **Parameters:**
**number** (*number*) – the numeric value to process
* **Returns:**
the integer value
* **Return type:**
number
### log(number[, base = e])
Return the logarithm of number to base.
* **Parameters:**
* **number** (*number*) – the numeric value to process
* **base** (*number*) – the base
* **Returns:**
the logarithm value
* **Return type:**
number
### pow(base, exponent)
Return base raised to the power of the exponent.
* **Parameters:**
* **base** (*number*) – the base
* **exponent** (*number*) – the exponent
* **Returns:**
the power of
* **Return type:**
number
> **See also**
>
> It’s significantly faster to use the `**` operator since it’s an operator and not a function.
### round(number[, decimals = 0])
Return number rounded to precision of decimals.
* **Parameters:**
* **number** (*number*) – the numeric value to process
* **decimals** (*number*) – the number of decimals
* **Returns:**
the rounded value
* **Return type:**
number
### sqrt(number)
Return the square root of number.
* **Parameters:**
**number** (*number*) – the numeric value to process
* **Returns:**
the square root
* **Return type:**
number
> **See also**
>
> It’s significantly faster to use the `number ** 0.5` operator to get the square root since it’s an operator and not a function.
## MIME
### *class* MIME
This is a MIME “string builder” used to construct MIME parts.
#### constructor()
The MIME object “constructor” takes no function arguments.
```hsl
$part = MIME();
$part->setType("multipart/alternative");
$part->appendPart(MIME()->setType("text/plain")->setBody("*Hello World*"));
$part->appendPart(MIME()->setType("text/html")->setBody("Hello World"));
echo $part->toString();
```
> **Note**
>
> Many of the MIME object’s member functions return *this*, allowing them to be called with method chaining.
>
> ```hsl
> echo MIME()->addHeader("Subject", "Hello")->setBody("Hello World")->toString();
> ```
#### addHeader(name, value[, options])
Add a header. The value may be encoded (if needed) and reformatted.
* **Parameters:**
* **name** (*string*) – name of the header
* **value** (*string*) – value of the header
* **options** (*array*) – an options array
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
The following options are available in the options array.
> * **encode** (boolean) Fold and encode the header. The default is `true`.
> * **encoding** (string) Encoding to use (base64 or quoted-printable). The default is `quoted-printable`.
> **Note**
>
> If a *Content-Type* header is added, the value of [`MIME.setType()`](#functions:MIME.setType) is ignored. If a *Content-Transfer-Encoding* header is added it will be used as the default encoding for the [`MIME.setBody()`](#functions:MIME.setBody).
#### appendPart(part)
Add a MIME part (child) object, this is useful when building a multipart MIME.
* **Parameters:**
**part** ([*MIME*](#functions:MIME)) – a MIME part object
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
> **Note**
>
> If not explicit set the *Content-Type* is set to *multipart/mixed*. The MIME boundary is also automatically created.
#### setBody(body)
Set the MIME part body content. In case the MIME part has children (multipart) this will be the MIME parts preamble. The body will be encoded as either quoted-printable or base64 depending on the type of data if no *Content-Transfer-Encoding* header is explicitly added.
* **Parameters:**
**body** (*string*) – the body
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### setType(type)
Set the type field of the *Content-Type* header. The default type is *text/plain*, and for *text/…* the charset is always utf-8.
* **Parameters:**
**type** (*string*) – the content type
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### setBoundary(boundary)
Set the MIME boundary for `multipart/*` messages. The default is to use an UUID.
* **Parameters:**
**boundary** (*string*) – the boundary
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### setDate([timestamp])
Add a Date header field. The default date is the current time.
* **Parameters:**
**timestamp** (*number*) – the unix timestamp
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### setFileName(filename)
Set the file name in the *Content-Disposition* header. The default disposition is *attachment*, but can be changed with [`MIME.setDisposition()`](#functions:MIME.setDisposition).
* **Parameters:**
**filename** (*string*) – the filename
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### setDisposition(type)
Set the disposition type in the *Content-Disposition* header.
* **Parameters:**
**type** (*string*) – the disposition type
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### signDKIM(selector, domain, key[, options])
Sign the MIME structure (message) using DKIM.
* **Parameters:**
* **selector** (*string*) – selector to use when signing
* **domain** (*string*) – domain to use when signing
* **key** (*resource* *or* *string*) – private key to use, either a `*PrivateKey` resource, `pki:X` (or a private RSA key in PEM format or a ed25519 key binary/base64 encoded, but not recommended).
* **options** (*array*) – options array
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
The following options are available in the options array.
> * **canonicalization_header** (string) header canonicalization (`simple` or `relaxed`). The default is `relaxed`.
> * **canonicalization_body** (string) body canonicalization (`simple` or `relaxed`). The default is `relaxed`.
> * **algorithm** (string) algorithm to hash the message with (`rsa-sha1`, `rsa-sha256` or `ed25519-sha256`). The default is `rsa-sha256`.
> * **additional_headers** (array) additional headers to sign in addition to those recommended by the RFC.
> * **exclude_headers** (array) exclude headers to sign from those recommended by the RFC.
> * **oversign_headers** (array) headers to oversign. The default is `from`.
> * **timestamp** (boolean or number) add the `t` tag to the signature (may be an absolute unix timestamp). The default is `false`.
> * **expiration** (number) add the `x` tag to the signature (an absolute unix timestamp). The default is no expiration.
> * **expiration_relative** (number) add the `x` tag to the signature (seconds relative to `timestamp` or current time). The default is no expiration.
> * **identity** (string) add the `i` tag to the signature. The default is no identity tag.
> * **headers** (array) headers to sign. Note that including `from` in the array is [required](https://datatracker.ietf.org/doc/html/rfc6376#section-5.4) when using this option. The default is to sign all headers recommended by the RFC.
> * **body_length** (number) length of the body to sign. The default is to sign the entire message.
> * **id** (boolean) If the key is given as an id of an private key in the configuration. The default is auto detect.
> * **additional_signatures** (array) An array of associative arrays for additional dual signing for the same canonicalization and settings.
> > * **selector** (string) selector to use when signing (required)
> > * **domain** (string) domain to use when signing (required)
> > * **key** (resource or string) private key to use, either a `*PrivateKey` resource, `pki:X` (or a private RSA key in PEM format or a ed25519 key binary/base64 encoded, but not recommended). (required)
> > * **identity** (string or none) add the `i` tag to the signature. The default is to inherit the primary identity tag. Use `none` to not add a identity tag.
> > * **id** (boolean) If the key is given as an string, you may set `id` to `true` and not prefix the value with `pki:`. The default is auto detect.
> > * **algorithm** (string) Only if the key is given as PEM or base64 data, you should the algorithm (`rsa` or `ed25519`).
> **Note**
>
> In the message the primary `DKIM-Signature` will be added first, and any additional will be prepended before the primary signature. The reason is that mail headers are added and reasoned about bottom up, similar to the top-most Received header is the latest one.
>
> ```default
> DKIM-Signature: second-additional-signature
> DKIM-Signature: first-additional-signature
> DKIM-Signature: primary-signature
> Subject: Hello World
>
> Message Content
> ```
#### toString()
Return the created MIME as a string. This function useful for debugging.
* **Returns:**
the MIME as string
* **Return type:**
string
#### toFile()
Return a [`File`](#functions:File) class for the current MIME object.
* **Returns:**
A File class for the current MIME object.
* **Return type:**
[`File`](#functions:File)
#### setContext(context)
Set immutable context data for the message. Available in queue hooks and shared between recipients.
* **Parameters:**
**context** (*associative array* *of* *strings*) – context data
* **Returns:**
this
* **Return type:**
[`MIME`](#functions:MIME)
#### queue(sender, recipient, transportid[, options])
Queue the message.
* **Parameters:**
* **sender** (*string* *or* *array*) – the sender email address, either as a string or an associative array with a `localpart` and `domain`. The domain support internationalized domain names (U-labels).
* **recipient** (*string* *or* *array*) – the recipient email address, either as a string or an associative array with a `localpart` and `domain`. The domain support internationalized domain names (U-labels).
* **transportid** (*string*) – the transport profile ID
* **options** (*array*) – an options array
* **Returns:**
an id object (with `transaction` and `queue`)
* **Return type:**
array
The following options are available in the options array.
> * **metadata** (array) Add metadata to the queued message, as a key-value pair array of strings.
> * **hold** (boolean) Put the message in the hold (inactive) queue. The default is `false`.
> * **tenantid** (string) Assign a tenantid to the message.
> * **jobid** (string) Assign a jobid to the message.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **quotas** (array) An array of quotas to be associated with the message, for use with [`queue_quota()`](#functions:queue_quota).
> * **delay** (number) Delay the first delivery attempt, in seconds. The default is `0`.
> * **dsn** (array) Associative array of `envid` (string), `ret` (string), `notify` (string) and `orcpt` (string).
```hsl
MIME()
->addHeader("Subject", "Hello")
->setBody("Hi, how are you?")
->queue("", ["localpart" => "info", "domain" => "example.com"], "outbound");
```
#### send(sender, recipients, server)
Try to send the message to the server. See [`MailMessage.send()`](#functions:MailMessage.send).
### class MailMessage : [MIMEPart](#functions:_CPPv48MIMEPart)
This class extends the [`MIMEPart`](#functions:_CPPv48MIMEPart) class, all instances of this class automatically holds a reference to the top level MIMEPart object.
#### reset()
Undo all changes on the message.
* **Returns:**
number of changes discarded
* **Return type:**
number
```hsl
$mail->reset();
```
#### snapshot()
Take a snapshot of the current state of the MIME object (to be used with [`MailMessage.restore()`](#functions:MailMessage.restore)).
* **Returns:**
the snapshot id
* **Return type:**
number
```hsl
$id = $mail->snapshot();
```
#### restore(id)
Restore to a snapshot (to be used with [`MailMessage.snapshot()`](#functions:MailMessage.snapshot)).
* **Parameters:**
**id** (*number*) – snapshot id
* **Returns:**
if restore was successful
* **Return type:**
boolean
```hsl
$mail->restore($id);
```
#### modifyContent(start, count, data)
An advanced method of adding custom modification that will be applied to the message (it works the same way as other modification function, eg. [`MIMEPart.addHeader()`](#functions:MIMEPart.addHeader)). This function should not be used when there are [`MIMEPart`](#functions:_CPPv48MIMEPart) operations available doing the supposed modification.
* **Parameters:**
* **start** (*number*) – start offset of modification
* **count** (*number*) – bytes to remove at position
* **data** (*string*) – data to add at position
* **Returns:**
this
* **Return type:**
[`MailMessage`](#functions:_CPPv411MailMessage)
```hsl
$mail = MailMessage::String("Subject: Hello\r\n\r\nHello");
$mail->modifyContent(18, 5, "World");
echo $mail->toString();
```
#### toFile()
Return a [`File`](#functions:File) class for the current MIME object (with all changes applied).
* **Returns:**
A File class for the current MIME object.
* **Return type:**
[`File`](#functions:File)
#### toString()
Return the MailMessage as a string (with all changes applied).
* **Returns:**
the MailMessage as string
* **Return type:**
string
#### signDKIM(selector, domain, key[, options])
Sign the message using DKIM. On error `none` is returned.
* **Parameters:**
* **selector** (*string*) – selector to use when signing
* **domain** (*string*) – domain to use when signing
* **key** (*resource* *or* *string*) – private key to use, either a `*PrivateKey` resource, `pki:X` (or a private RSA key in PEM format or a ed25519 key binary/base64 encoded, but not recommended).
* **options** (*array*) – options array
* **Returns:**
this
* **Return type:**
[`MailMessage`](#functions:_CPPv411MailMessage) or none
The following options are available in the options array.
> * **canonicalization_header** (string) header canonicalization (`simple` or `relaxed`). The default is `relaxed`.
> * **canonicalization_body** (string) body canonicalization (`simple` or `relaxed`). The default is `relaxed`.
> * **algorithm** (string) algorithm to hash the message with (`rsa-sha1`, `rsa-sha256` or `ed25519-sha256`). The default is `rsa-sha256`.
> * **additional_headers** (array) additional headers to sign in addition to those recommended by the RFC.
> * **exclude_headers** (array) exclude headers to sign from those recommended by the RFC.
> * **oversign_headers** (array) headers to oversign. The default is `from`.
> * **timestamp** (boolean or number) add the `t` tag to the signature (may be an absolute unix timestamp). The default is `false`.
> * **expiration** (number) add the `x` tag to the signature (an absolute unix timestamp). The default is no expiration.
> * **expiration_relative** (number) add the `x` tag to the signature (seconds relative to `timestamp` or current time). The default is no expiration.
> * **identity** (string) add the `i` tag to the signature. The default is no identity tag.
> * **headers** (array) headers to sign. The default is to sign all headers recommended by the RFC.
> * **body_length** (number) length of the body to sign. The default is to sign the entire message.
> * **id** (boolean) If the key is given as an id of an private key in the configuration. The default is auto detect.
> * **return_header** (boolean) Return the DKIM signature as a string, instead of adding it to the message. The default is `false`.
> * **arc** (number) Create an ARC-Message-Signature header by setting the ARC `i` field.
> * **additional_signatures** (array) An array of associative arrays for additional dual signing for the same canonicalization and settings.
> > * **selector** (string) selector to use when signing (required)
> > * **domain** (string) domain to use when signing (required)
> > * **key** (resource or string) private key to use, either a `*PrivateKey` resource, `pki:X` (or a private RSA key in PEM format or a ed25519 key binary/base64 encoded, but not recommended). (required)
> > * **identity** (string or none) add the `i` tag to the signature. The default is to inherit the primary identity tag. Use `none` to not add a identity tag.
> > * **id** (boolean) If the key is given as an string, you may set `id` to `true` and not prefix the value with `pki:`. The default is auto detect.
> > * **algorithm** (string) Only if the key is given as PEM or base64 data, you should the algorithm (`rsa` or `ed25519`).
> **Note**
>
> In the message the primary `DKIM-Signature` will be added first, and any additional will be prepended before the primary signature. The reason is that mail headers are added and reasoned about bottom up, similar to the top-most Received header is the latest one.
>
> ```default
> DKIM-Signature: second-additional-signature
> DKIM-Signature: first-additional-signature
> DKIM-Signature: primary-signature
> Subject: Hello World
>
> Message Content
> ```
> **Note**
>
> If *return_header* is used, you need to add the header yourself without encoding.
>
> ```hsl
> $dkimsig = $message->signDKIM("selector", "example.com", $key, ["return_header" => true]);
> $message->addHeader("DKIM-Signature", $dkimsig, ["encode" => false]);
> ```
> **Note**
>
> Example commands to generate an *ed25519* public/private key pair with OpenSSL.
>
> ```hsl
> # openssl genpkey -algorithm ed25519 -out dkim_ed25519.private
> # openssl pkey -outform PEM -pubout -in dkim_ed25519.private
> -----BEGIN PUBLIC KEY-----
> MCowBQYDK2VwAyEAt+BaReQe/Xs/jNbhGJre2Y1O4EUsHauy2Ygi5s3gc/0=
> -----END PUBLIC KEY-----
> # openssl pkey -outform DER -in dkim_ed25519.private | tail -c +17 | openssl base64
> NvGwmGmY/vdZH75jDZs9HXe6RZUEx07BeOg3q1ffNRA=
> ```
#### verifyDKIM(headerfield[, options])
DKIM verify a *DKIM-Signature* or *ARC-Message-Signature* header. The header should include both the header name and value (unmodified).
* **Parameters:**
* **headerfield** (*string*) – the header to verify
* **options** (*array*) – options array
* **Returns:**
associative array containing the result.
* **Return type:**
array
The following options are available in the options array.
> * **dns_function** (function) a custom DNS function. The default is to use the built in.
The DNS function will be called with the hostname (eg. *2018._domainkeys.example.com*) for which a DKIM record should be returned. The result must be an array containing either an `error` field (`permerror` or `temperror`) or a `result` field with a DKIM TXT record as string.
The resulting array always contains a `result` field of either `pass`, `permerror`, `passerror` (softfail) or `temperror`. In case of an error the reason is included in an `error` field. If the header was successfully parsed (regardless of the result) a `tags` field will be included.
> **Note**
>
> This function will suspend the current script execution while waiting for DNS responses allowing for cooperative multitasking.
#### setContext(context)
Set immutable context data for the message. Available in queue hooks and shared between recipients.
* **Parameters:**
**context** (*associative array* *of* *strings*) – context data
* **Returns:**
this
* **Return type:**
[`MailMessage`](#functions:_CPPv411MailMessage)
Return the MailMessage as a string (with all changes applied).
#### send(sender, recipients, server)
Try to send the message to the server.
* **Parameters:**
* **sender** (*string* *or* *array*) – the sender (*MAIL FROM*), an address object
* **recipients** (*array* *of* *(**string* *or* *array* *)*) – the recipient (*RCPT TO*), an array of address objects
* **server** (*string* *or* *array*) – array with server settings or transport profile ID
* **Returns:**
associative array containing the result or an error
* **Return type:**
array
The address parameters should be either a string or an associative array with a `localpart` and `domain` and optionally a `params` field as an key-values array (to be sent in the *MAIL FROM* or *RCPT TO* command). The domain support internationalized domain names (U-labels).
Transport profiles configured to use destination lookup by MX are not supported, see the note below.
```hsl
$response = $message->send(
["localpart" => "nick", "domain" => "example.org"],
[
["localpart" => "chris", "domain" => "example.com", "params" => ["NOTIFY" => "DELAY"]],
["localpart" => "charlie", "domain" => "example.com"],
],
["server" => "10.2.0.1", "tls" => "require"]);
if (isset($response["result"]))
{
$result = $response["result"];
$codes = [];
if ($result["state"] == "EOD")
$codes = ["reply_codes" => ["code" => $result["code"], "enhanced" => $result["enhanced"]]];
if ($result["code"] >= 200 and $result["code"] <= 299)
Accept([...$codes, "reason" => $result["reason"]]);
if ($result["code"] >= 500 and $result["code"] <= 599)
Reject($result["reason"], $codes);
Defer($result["reason"], $codes);
}
else
{
$error = $response["error"];
if (!$error["temporary"])
Reject($error["message"]);
Defer($error["message"]);
}
```
The following server settings are available in the server array.
> * **server** (string or array) The destination IP-address or hostname as a string, or an associative array with `host` (string) and `mx` (boolean). There is no default destination server.
> * **port** (number) TCP port. The default is `25`.
> * **helo** (string) The default is to use the system hostname.
> * **sourceip** (string) Explicitly bind an IP address. The default is to be chosen by the system.
> * **sourceipid** (string) Explicitly bind an IP address ID. The default is to be chosen by the system.
> * **nonlocal_source** (boolean) Allow binding of non-local addresses (BINDANY). The default is `false`.
> * **saslusername** (string) If specified issue a AUTH LOGIN before MAIL FROM.
> * **saslpassword** (string) If specified issue a AUTH LOGIN before MAIL FROM.
> * **saslmechanism** (string) If specified issue the SASL mechanism and the saslpassword (used for eg. XOAUTH2).
> * **tls** (string) Use any of the following TLS modes; `disabled`, `optional`, `optional_verify`, `dane`, `dane_fallback_require`, `dane_fallback_require_verify`, `dane_require`, `require`, `require_verify` or `trust`. The default is `disabled`.
> * **tls_implicit** (boolean) Use implicit TLS (do not use STARTTLS). The default is `false`.
> * **tls_trust_anchor** (array) An array containing *X509Resource* and/or associative arrays of `selector` (The trust selector), `mtype` (The trust match type) and `data` (The trust data (hex encoded)).
> * **tls_sni** (string or boolean) Request a certificate using the SNI extension. If `true` the connected hostname will be used. The default is not to use SNI (`false`).
> * **tls_protocols** (string) Use one or many of the following TLS protocols; `SSLv2`, `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2` or `TLSv1.3`. Protocols may be separated by `,` and excluded by `!`. The default is `!SSLv2,!SSLv3`.
> * **tls_ciphers** (string) List of ciphers to support. The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_ciphersuites** (string) List of TLS ciphersuites to support (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_groups** (string) List of TLS groups to offer for TLS key exchange. (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_verify_host** (boolean) Verify certificate hostname (CN). The default is `false`.
> * **tls_verify_name** (array) Hostnames to verify against the certificate’s CN and SAN (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **tls_verify_name_include_host** (boolean) Include the peer you’re connecting to in the verify name list. Unlink `tls_verify_host` which requires it to match regardless of items in the list. The default is `false`.
> * **tls_client_cert** (array or string) If given as an associative array it should include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. If given as a string use the following id of a certificate in the configuration as client certificate. The default is to not send a client certificate.
> * **xclient** (array) Associative array of XCLIENT attributes to send.
> * **chunking** (boolean) Enable CHUNKING support. The default is `true`.
> * **pipelining** (boolean) Enable PIPELINING support. The default is `true`.
> * **protocol** (string) The protocol to use; `smtp` or `lmtp`. The default is `smtp`.
> * **mx_include** (array) Filter the MX lookup result, only including ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude** (array) Filter the MX lookup result, removing ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude_bypass** (array) Before applying `mx_exclude`, allow the once matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **ip_exclude** (array) Filter the IP addresses or networks, removing addresses listed.
> * **ip_exclude_temporary** (boolean) Specify if empty results due to filtered IP or MX addresses should result in a temporary or permanent error. The default is `false` (permanent error).
> * **timeout** (array) Associative array of [state](#postdelivery:as1) and the timeout in seconds. The default is set according to RFC2821.
> * **connect_timeout** (number) The connect timeout in seconds. The default is `30` seconds.
> * **proxyprotocol** (array) Associative array of `server` (IP), `port` as well as local `sourceip` to use when connecting to the outbound PROXY server.
If the send function resulted in a SMTP response you will get the SMTP response in a `result` field. This `result` field contains a `state` field (string) which indicates at what SMTP stage the error happened, a `reason` field (array of strings) containing the SMTP reponse (from the server) and a `code` field (number) containg the SMTP status code, optionally a `enhanced` (array of three numbers) field containg the SMTP enhanced status code. If a generic error happens the function will return a `error` field. This `error` field contains a `temporary` (boolean) field to indicate if the error may be transient, a `reason` field (string) containing a the error which happened and a `type` field (string) containing the type of error which happened (one of `config`, `dns`, `network`, `dane`, `protocol`, `smtp`, `tls` or `plugin`), for *dns* errors a subcategories is presented with a `dns` properites with one of the following value of `other`, `nxdomain`, `nullmx`, `ipfamily`, `nodata` or `excluded`.
If a SMTP connection could be established a `connection` field will be included. This field contains the `localip` field (string), the `localipid` field (string), the `remoteip` field (string) and the `remotemx` field (string).
A `tls` field will always be included, to indicate if the connection had TLS enabled.
The following `state` values are available.
| | |
|-----------------|------------------------------------------------|
| BANNER | The initial SMTP greeting |
| HELO | |
| EHLO | |
| LHLO | |
| STARTTLS | |
| AUTH-CRAM-MD5 | In reply to sending AUTH CRAM-MD5 command |
| AUTH-PLAIN | In reply to sending AUTH PLAIN command |
| AUTH-LOGIN | In reply to sending AUTH LOGIN command |
| AUTH-LOGIN-USER | In reply to sending AUTH LOGIN username |
| AUTH | In reply to last command of AUTH login attempt |
| XCLIENT | In reply to sending a XCLIENT command |
| MAIL | |
| RCPT | |
| DATA | In reply to sending the DATA command |
| BDAT | In reply to sending the BDAT command |
| EOD | In reply sending the End-of-DATA |
| RSET | |
| NOOP | |
| QUIT | |
> **Note**
>
> It’s not possible to use transport profiles configured to use destination lookup by MX, for the simple reason that this function supports
> sending to multiple recipients, on multiple domains/MX. You have to explicitly configure which host to deliver to.
> To use lookup MX for a specific domain use this server syntax.
>
> ```hsl
> $mail->send($sender, $recipients, ["server" => ["host" => "example.com", "mx" => true]]);
> ```
#### queue(sender, recipient, transportid[, options])
Queue the message.
* **Parameters:**
* **sender** (*string* *or* *array*) – the sender email address, either as a string or an associative array with a `localpart` and `domain`
* **recipient** (*string* *or* *array*) – the recipient email address, either as a string or an associative array with a `localpart` and `domain`
* **transportid** (*string*) – the transport profile ID
* **options** (*array*) – an options array
* **Returns:**
an id object (with `transaction` and `queue`)
* **Return type:**
array
The following options are available in the options array.
> * **metadata** (array) Add metadata to the queued message, as a key-value pair array of strings.
> * **hold** (boolean) Put the message in the hold (inactive) queue. The default is `false`.
> * **tenantid** (string) Assign a tenantid to the message.
> * **jobid** (string) Assign a jobid to the message.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **quotas** (array) An array of quotas to be associated with the message, for use with [`queue_quota()`](#functions:queue_quota).
> * **delay** (number) Delay the first delivery attempt, in seconds. The default is `0`.
> * **dsn** (array) Associative array of `envid` (string), `ret` (string), `notify` (string) and `orcpt` (string).
#### *static* String(data)
Return a MailMessage resource containing the data.
* **Parameters:**
**data** (*string*) – the content
* **Returns:**
A MailMessage resource
* **Return type:**
[`MailMessage`](#functions:_CPPv411MailMessage) or none
#### *static* File(file)
Return a MailMessage resource containing the file’s content.
* **Parameters:**
**file** ([*File*](#functions:File)) – the content
* **Returns:**
A MailMessage resource
* **Return type:**
[`MailMessage`](#functions:_CPPv411MailMessage) or none
### class MIMEPart
This class represent a MIME part in the MIME tree.
> **Note**
>
> This class can only be accessed through the extended [`MailMessage`](#functions:_CPPv411MailMessage) class or from functions returning this object type eg. [`MIMEPart.getParts()`](#functions:MIMEPart.getParts).
> **Note**
>
> Changes done to any MIME object will **not** be reflected on consecutive calls to “get” functions, however they will be applied to the message upon delivery.
> Also multiple calls to the same “set” function may yield undefined behaviour (like calling setBody twice).
#### getID()
Return the MIME part’s ID.
* **Returns:**
part id
* **Return type:**
string
#### getSize()
Return the MIME part’s size in bytes.
* **Returns:**
size in bytes
* **Return type:**
number
#### getFileName()
Return the MIME part’s file name (if it has one).
* **Returns:**
file name
* **Return type:**
string or none
#### getType()
Return the MIME part’s *Content-Type*’s type field (eg. *text/plain*).
* **Returns:**
content type
* **Return type:**
string or none
#### getTypeParameter(name)
Return the MIME part’s *Content-Type*’s type parameter value based on name (eg. *boundary*).
* **Parameters:**
**name** (*string*) – name of the parameter
* **Returns:**
content type
* **Return type:**
string or none
#### getHeader(name[, options])
Return the value of a header (if multiple headers with the same name exists, the topmost will be returned). If no header is found, the value `none` is returned. The name is not case sensitive.
* **Parameters:**
* **name** (*string*) – name of the header
* **options** (*array*) – an options array
* **Returns:**
header value
* **Return type:**
string or none
The following options are available in the options array.
> * **index** (number) The index of the header, from the top, starting at zero (per header name). The default is `0`.
> * **field** (boolean) Get the header field as is (including the name, CRLF and whitespace preserved). The default is `false`.
> * **decode** (boolean) Unfold and decode the header’s MIME words (`=?utf-8?q?...?=`). The default is `true`.
> * **reverse** (boolean) Get headers in reverse order. The default is `false`.
```hsl
if (is_string($contentid = $part->getHeader("Content-ID")))
echo "Content-ID is $contentid";
```
```hsl
// DKIM verify example
$dkimHeader = $part->getHeader("DKIM-Signature", ["field" => true]); // field needed for DKIM
if ($dkimHeader)
echo $part->verifyDKIM($dkimHeader);
```
> **Note**
>
> The [`MIMEPart.getHeader()`](#functions:MIMEPart.getHeader) function family will return headers as a UTF-8 string with all MIME encoded-words decoded (`=?charset?encoding?data?=`). However even if headers must be in 7-bit ASCII, some senders do not conform to this and do send headers with different charset encodings. In those cases we (1) Use the MIME-parts “Content-Type” headers charset when converting to UTF-8. (2) If there is no charset information available we use a statistical charset detection function. (3) We just pretend it to be US-ASCII and covert it to UTF-8 anyway (guaranteeing the result will be valid UTF-8).
#### getHeaders(name[, options])
Return a list of header values. If no header is found, an empty list is returned. The name is not case sensitive.
* **Parameters:**
* **name** (*string*) – name of the header
* **options** (*array*) – an options array
* **Returns:**
header values
* **Return type:**
array of string
The following options are available in the options array.
> * **field** (boolean) Get the header field as is (including the name). The default is `false`.
> * **decode** (boolean) Unfold and decode the header’s MIME words (`=?utf-8?q?...?=`). The default is `true`.
```hsl
echo "Received headers: ".length($arguments["mail"]->getHeaders("Received"));
```
#### getHeaderNames()
Return a list of all header names, from the top. The names are in lower case.
* **Returns:**
header names
* **Return type:**
array of string
#### setHeader(name, value[, options])
Overwrite existing header(s) or create a new header. The name is not case sensitive.
* **Parameters:**
* **name** (*string*) – name of the header
* **value** (*string*) – value of the header
* **options** (*array*) – an options array
* **Returns:**
number of headers changed
* **Return type:**
number
The following options are available in the options array.
> * **index** (number) The index of the header, from the top, starting at zero (per header name).
> * **encode** (boolean) Fold and encode the header. The default is `true`.
> * **encoding** (string) Encoding to use (base64 or quoted-printable). The default is `quoted-printable`.
#### addHeader(name, value[, options])
Add a new header (at the top of the message).
* **Parameters:**
* **name** (*string*) – name of the header
* **value** (*string*) – value of the header
* **options** (*array*) – an options array
* **Return type:**
none
The following options are available in the options array.
> * **encode** (boolean) Fold and encode the header. The default is `true`.
> * **encoding** (string) Encoding to use (base64 or quoted-printable). The default is `quoted-printable`.
#### delHeader(name[, options])
Delete all headers by the name. The name is not case sensitive.
* **Parameters:**
* **name** (*string*) – name of the header
* **options** (*array*) – an options array
* **Returns:**
number of headers deleted
* **Return type:**
number
The following options are available in the options array.
> * **index** (number) The index of the header, from the top, starting at zero (per header name).
#### renameHeader(name, newname[, options])
Rename all headers by the name. The name is not case sensitive.
* **Parameters:**
* **name** (*string*) – name of the header
* **newname** (*string*) – the new header name
* **Returns:**
number of headers renamed
* **Return type:**
number
#### setDateLater()
Add or set the Date header field upon message delivery/generation. The default date is the current time.
* **Returns:**
number of headers changed
* **Return type:**
number
> **Warning**
>
> If you also DKIM sign your messages when using this function it’s very important that you exclude the *Date* header from the DKIM signature using for example the `exclude_headers` option to [`MIME.signDKIM()`](#functions:MIME.signDKIM).
#### remove()
Remove this MIME part.
* **Return type:**
none
#### getBody([options])
Get the body (content) of a MIME part. The content will be decoded according to the *Content-Transfer-Encoding* header.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
the body content
* **Return type:**
string or none
The following options are available in the options array.
> * **decode** (boolean) Decode the body accoding to the “Content-Transfer-Encoding” header. The default is `true`.
> **Note**
>
> This function will decode using the “Content-Transfer-Encoding” header. It will not do any character set conversion, hence the data can be in any character set encoding.
#### setBody(data[, options])
Set the body (content) of a MIME part. The MIME parts encoding (*Content-Transfer-Encoding*) will be changed to the best readable match, that can be either *7bit*, *quoted-printable* or *base64* and the data will encoded as such.
* **Parameters:**
* **data** (*string*) – the body content
* **options** (*array*) – an options array
* **Returns:**
this
* **Return type:**
[`MIMEPart`](#functions:_CPPv48MIMEPart) or none
The following options are available in the options array.
> * **encoding** (string) Force encoding of `7bit`, `8bit`, `binary`, `base64` or `quoted-printable`. The default is to use the best readable match.
> **Note**
>
> Changes done to any MIME object will **not** be reflected on consecutive calls to “get” functions, however they will be applied to the message upon delivery.
> Also multiple calls to the same “set” function may yield undefined behaviour (like calling setBody twice).
#### getPreamble()
Get the multipart MIME parts epilogue text. If the MIMEPart isn’t a multipart MIME, `none` is returned.
* **Return type:**
string or none
#### setPreamble(data)
Set the multipart MIME parts preamble text. If the MIMEPart isn’t a multipart MIME, `none` is returned.
* **Parameters:**
**data** (*string*) – the preamble text
* **Returns:**
this
* **Return type:**
[`MIMEPart`](#functions:_CPPv48MIMEPart) or none
#### getEpilogue()
Get the multipart MIME parts epilogue text. If the MIMEPart isn’t a multipart MIME, `none` is returned.
* **Return type:**
string or none
#### setEpilogue(data)
Set the multipart MIME parts epilogue text. If the MIMEPart isn’t a multipart MIME, `none` is returned.
* **Parameters:**
**data** (*string*) – the epilogue text
* **Returns:**
this
* **Return type:**
[`MIMEPart`](#functions:_CPPv48MIMEPart) or none
#### prependPart(part[, options])
Add a MIME part before this part. If the MIMEPart isn’t a multipart MIME, `none` is returned.
* **Parameters:**
* **part** ([*MIME*](#functions:MIME)) – a [`MIME`](#functions:MIME) or [`MIMEPart`](#functions:_CPPv48MIMEPart) object
* **options** (*array*) – an options array
* **Returns:**
this
* **Return type:**
[`MIMEPart`](#functions:_CPPv48MIMEPart) or none
The following options are available in the options array.
> * **type** (string) The multipart content type to use. The default is `multipart/mixed`.
#### appendPart(part[, options])
Add a MIME part after this part. If the MIMEPart isn’t a multipart MIME, `none` is returned.
* **Parameters:**
* **part** ([*MIME*](#functions:MIME)) – a [`MIME`](#functions:MIME) or [`MIMEPart`](#functions:_CPPv48MIMEPart) object
* **options** (*array*) – an options array
* **Returns:**
this
* **Return type:**
[`MIMEPart`](#functions:_CPPv48MIMEPart) or none
The following options are available in the options array.
> * **type** (string) The multipart content type to use. The default is `multipart/mixed`.
#### replacePart(part)
Replace the current MIME part.
* **Parameters:**
**part** ([*MIME*](#functions:MIME)) – a [`MIME`](#functions:MIME) or [`MIMEPart`](#functions:_CPPv48MIMEPart) object
* **Return type:**
none
#### findByType(type)
Find descendant parts (on any depth) based on their *Content-Type*. If type is given as a string, it’s compiled as a regex with `/type/i` (partial, case-insensitive).
* **Parameters:**
**type** ([Regex](#literals:regextype) or string) – type as regex
* **Returns:**
MIME parts
* **Return type:**
array of [`MIMEPart`](#functions:_CPPv48MIMEPart) objects
#### findByFileName(filename)
Find descendant parts (on any depth) based on their file name. If filename is given as a string, it’s compiled as a regex with `/filename/i` (partial, case-insensitive).
* **Parameters:**
**filename** ([Regex](#literals:regextype) or string) – filename as regex
* **Returns:**
MIME parts
* **Return type:**
array of [`MIMEPart`](#functions:_CPPv48MIMEPart) objects
#### getByID(id)
Get a MIME part by ID (on any depth, including the current part). If the part is not found `none` is returned.
* **Parameters:**
**id** (*string*) – the MIME part id
* **Returns:**
MIME part
* **Return type:**
[`MIMEPart`](#functions:_CPPv48MIMEPart) object or none
#### getParts()
Return child parts.
* **Returns:**
MIME parts
* **Return type:**
array of [`MIMEPart`](#functions:_CPPv48MIMEPart) objects
#### getErrors()
Return a list of all errors affecting this MIME part. The following errors are available; `bad_header` and `unclosed_boundary`.
* **Returns:**
list of errors
* **Return type:**
array of string
## Misc
### gethostname()
The hostname of the installation, this can be used to identify a software instance.
* **Returns:**
the hostname
* **Return type:**
string
### uuid()
Return a unique ID.
* **Returns:**
a unique ID
* **Return type:**
string
### uuidv5(uuidnamespace, value)
Return a deterministic unique ID.
* **Parameters:**
* **uuidnamespace** (*string*) – the UUIDv5 namespace
* **value** (*string*) – the value to hash
* **Returns:**
a unique ID
* **Return type:**
string
```hsl
echo uuidv5("6ba7b810-9dad-11d1-80b4-00c04fd430c8", "halon.io"); // 97665414-d057-5f50-9a76-a3dc23d321a1
```
### echo()
Print a message to the log.
```hsl
echo "Log message";
```
> **Note**
>
> *echo* is not a function, therefore do not call it with parentheses. The text messages is logged with [`syslog()`](#functions:syslog) level *debug* or stdout, with `$messageid` prefixed (when applicable).
### syslog(priority, message)
The syslog function complements the `echo` statement by allowing messages with custom priorities to be logged.
* **Parameters:**
* **priority** (*string* *or* *number*) – message priority
* **message** (*string*) – message
* **Return type:**
none
Priority may be any of
| Name | |
|---------|----|
| emerg | 0 |
| alert | 1 |
| crit | 2 |
| err | 3 |
| warning | 4 |
| notice | 5 |
| info | 6 |
| debug | 7 |
It’s possible to change the facility of a log message by adding a facility value (see rfc5424).
```hsl
syslog(3 + (4<<3), "This is sent as LOG_ERR to LOG_AUTH");
```
> **Note**
>
> If you want your log message to appear when the message log is viewed (as it does with [`echo()`](#functions:echo), you should prefix the message parameter with `"[$messageid] "`.
### inet_includes(ip, network)
Returns true if *ip* is in the subnet or range of *network*. Both IPv4 and IPv6 are supported (comparison between different protocols always yields *false*). On error `none` is returned.
* **Parameters:**
* **ip** (*string*) – ip address
* **network** (*string*) – address, subnet or range.
* **Returns:**
true if ip is in network
* **Return type:**
boolean or none
```hsl
inet_includes("127.0.0.1", "127.0.0.1/8");
inet_includes("127.0.0.1", "127.0.0.0-127.255.255.255");
inet_includes("127.0.0.1", "127.0.0.1");
inet_includes("2001:4860:4860::8888", "2001:4860:4860::/48");
```
### inet_ntop(ip)
Converts an IP from a binary string format (4 char for IPv4 and 16 char for IPv6) to a printable string format (eg *10.0.0.1*). On error `none` is returned.
* **Parameters:**
**ip** (*string*) – the ip in binary string format
* **Returns:**
an ip in printable string format
* **Return type:**
string or none
### inet_pton(ip)
Converts an IP from printable string format (eg *10.0.0.1*) to a binary string format (4 char for IPv4 and 16 char for IPv6). On error `none` is returned.
* **Parameters:**
**ip** (*string*) – the ip in printable format
* **Returns:**
an ip in binary string format
* **Return type:**
string or none
```hsl
$x = unpack("N*", inet_pton($ip));
if (length($x) == 1)
$x[0] = $x[0] & 0xffffff00; // mask ipv4 to /24
if (length($x) == 4)
$x[3] = 0; // mask ipv6 to /96
echo inet_ntop(pack("N*", ...$x));
```
### inet_reverse(ip[, zone])
> Converts an IPv4 or IPv6 to a reverse DNS compatible format (to be used with PTR lookups or DNSxL lookups). By default the zone correspons to the ARPA address for each IP family. On error `none` is returned.
* **Parameters:**
* **ip** (*string*) – the ip in printable format
* **zone** (*string*) – the zone to append
* **Returns:**
an reverse DNS hostname
* **Return type:**
string or none
```hsl
echo inet_reverse("8.8.8.8"); // 8.8.8.8.in-addr.arpa
echo inet_reverse("12.34.56.78", "example.com"); // 78.56.34.12.example.com
```
## Protocols
### smtp_lookup_rcpt(server, sender, recipient[, options])
Check if sender is allowed to send mail to recipient.
* **Parameters:**
* **server** (*string* *or* *array*) – array with server settings or transport profile ID
* **sender** (*string* *or* *array*) – the sender (MAIL FROM), either as a string or an associative array with a `localpart` and `domain`
* **recipient** (*string* *or* *array*) – the recipient (RCPT TO), either as a string or an associative array with a `localpart` and `domain`
* **options** (*array*) – options array
* **Returns:**
`1` if the command succeeded, `0` if the command failed and `-1` if an error occurred. The `extended_result` option may change this behaviour.
* **Return type:**
number or array
The following server settings are available in the server array.
> * **server** (string or array) The destination IP-address or hostname as a string, or an associative array with `host` (string) and `mx` (boolean). The default is to use MX lookup for the recipient domain.
> * **port** (number) TCP port. The default is `25`.
> * **helo** (string) The default is to use the system hostname.
> * **sourceip** (string) Explicitly bind an IP address. The default is to be chosen by the system.
> * **sourceipid** (string) Explicitly bind an IP address ID. The default is to be chosen by the system.
> * **nonlocal_source** (boolean) Allow binding of non-local addresses (BINDANY). The default is `false`.
> * **saslusername** (string) If specified issue a AUTH LOGIN before MAIL FROM.
> * **saslpassword** (string) If specified issue a AUTH LOGIN before MAIL FROM.
> * **saslmechanism** (string) If specified issue the SASL mechanism and the saslpassword (used for eg. XOAUTH2).
> * **tls** (string) Use any of the following TLS modes; `disabled`, `optional`, `optional_verify`, `dane`, `dane_fallback_require`, `dane_fallback_require_verify`, `dane_require`, `require`, `require_verify` or `trust`. The default is `disabled`.
> * **tls_implicit** (boolean) Use implicit TLS (do not use STARTTLS). The default is `false`.
> * **tls_trust_anchor** (array) An array containing *X509Resource* and/or associative arrays of `selector` (The trust selector), `mtype` (The trust match type) and `data` (The trust data (hex encoded)).
> * **tls_sni** (string or boolean) Request a certificate using the SNI extension. If `true` the connected hostname will be used. The default is not to use SNI (`false`).
> * **tls_protocols** (string) Use one or many of the following TLS protocols; `SSLv2`, `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2` or `TLSv1.3`. Protocols may be separated by `,` and excluded by `!`. The default is `!SSLv2,!SSLv3`.
> * **tls_ciphers** (string) List of ciphers to support. The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_ciphersuites** (string) List of TLS ciphersuites to support (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_groups** (string) List of TLS groups to offer for TLS key exchange. (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_verify_host** (boolean) Verify certificate hostname (CN). The default is `false`.
> * **tls_verify_name** (array) Hostnames to verify against the certificate’s CN and SAN (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **tls_verify_name_include_host** (boolean) Include the peer you’re connecting to in the verify name list. Unlink `tls_verify_host` which requires it to match regardless of items in the list. The default is `false`.
> * **tls_client_cert** (array or string) If given as an associative array it should include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. If given as a string use the following id of a certificate in the configuration as client certificate. The default is to not send a client certificate.
> * **tls_capture_peer_cert** (boolean) If set to true, the peer certificate will be available in the extended results. The default is `true`.
> * **xclient** (array) Associative array of XCLIENT attributes to send.
> * **protocol** (string) The protocol to use; `smtp` or `lmtp`. The default is `smtp`.
> * **mx_include** (array) Filter the MX lookup result, only including ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude** (array) Filter the MX lookup result, removing ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude_bypass** (array) Before applying `mx_exclude`, allow the once matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **ip_exclude** (array) Filter the IP addresses or networks, removing addresses listed.
> * **ip_exclude_temporary** (boolean) Specify if empty results due to filtered IP or MX addresses should result in a temporary or permanent error. The default is `false` (permanent error).
> * **timeout** (array) Associative array of [state](#postdelivery:as1) and the timeout in seconds. The default is set according to RFC2821.
> * **connect_timeout** (number) The connect timeout in seconds. The default is `30` seconds.
> * **proxyprotocol** (array) Associative array of `server` (IP) and `port` as well as local `sourceip` to use when connecting to the outbound PROXY server.
The following options are available in the options array.
> * **extended_result** (boolean) If `true` an associative array with `error_code`, `error_message`, `on_rcptto` and `tls` is returned. The default is `false`.
### smtp_lookup_auth(server, username, password)
Try to authenticate the username against a SMTP server.
* **Parameters:**
* **server** (*string* *or* *array*) – array with server settings or transport profile ID
* **username** (*string*) – username
* **password** (*string*) – password
* **Returns:**
`1` if the authentication succeeded, `0` if the authentication failed and `-1` if an error occurred.
* **Return type:**
number
The following server settings are available in the server array.
> * **server** (string or array) The destination IP-address or hostname as a string, or an associative array with `host` (string) and `mx` (boolean). There is no default destination server.
> * **port** (number) TCP port. The default is `25`.
> * **helo** (string) The default is to use the system hostname.
> * **sourceip** (string) Explicitly bind an IP address. The default is to be chosen by the system.
> * **sourceipid** (string) Explicitly bind an IP address ID. The default is to be chosen by the system.
> * **nonlocal_source** (boolean) Allow binding of non-local addresses (BINDANY). The default is `false`.
> * **saslusername** (string) If specified issue a AUTH LOGIN before MAIL FROM.
> * **saslpassword** (string) If specified issue a AUTH LOGIN before MAIL FROM.
> * **saslmechanism** (string) If specified issue the SASL mechanism and the saslpassword (used for eg. XOAUTH2).
> * **tls** (string) Use any of the following TLS modes; `disabled`, `optional`, `optional_verify`, `dane`, `dane_fallback_require`, `dane_fallback_require_verify`, `dane_require`, `require`, `require_verify` or `trust`. The default is `disabled`.
> * **tls_implicit** (boolean) Use implicit TLS (do not use STARTTLS). The default is `false`.
> * **tls_trust_anchor** (array) An array containing *X509Resource* and/or associative arrays of `selector` (The trust selector), `mtype` (The trust match type) and `data` (The trust data (hex encoded)).
> * **tls_sni** (string or boolean) Request a certificate using the SNI extension. If `true` the connected hostname will be used. The default is not to use SNI (`false`).
> * **tls_protocols** (string) Use one or many of the following TLS protocols; `SSLv2`, `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2` or `TLSv1.3`. Protocols may be separated by `,` and excluded by `!`. The default is `!SSLv2,!SSLv3`.
> * **tls_ciphers** (string) List of ciphers to support. The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_ciphersuites** (string) List of TLS ciphersuites to support (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_groups** (string) List of TLS groups to offer for TLS key exchange. (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_verify_host** (boolean) Verify certificate hostname (CN). The default is `false`.
> * **tls_verify_name** (array) Hostnames to verify against the certificate’s CN and SAN (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **tls_verify_name_include_host** (boolean) Include the peer you’re connecting to in the verify name list. Unlink `tls_verify_host` which requires it to match regardless of items in the list. The default is `false`.
> * **tls_client_cert** (array or string) If given as an associative array it should include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. If given as a string use the following id of a certificate in the configuration as client certificate. The default is to not send a client certificate.
> * **xclient** (array) Associative array of XCLIENT attributes to send.
> * **protocol** (string) The protocol to use; `smtp` or `lmtp`. The default is `smtp`.
> * **mx_include** (array) Filter the MX lookup result, only including ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude** (array) Filter the MX lookup result, removing ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude_bypass** (array) Before applying `mx_exclude`, allow the once matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **ip_exclude** (array) Filter the IP addresses or networks, removing addresses listed.
> * **ip_exclude_temporary** (boolean) Specify if empty results due to filtered IP or MX addresses should result in a temporary or permanent error. The default is `false` (permanent error).
> * **timeout** (array) Associative array of [state](#postdelivery:as1) and the timeout in seconds. The default is set according to RFC2821.
> * **connect_timeout** (number) The connect timeout in seconds. The default is `30` seconds.
> * **proxyprotocol** (array) Associative array of `server` (IP) and `port` as well as local `sourceip` to use when connecting to the outbound PROXY server.
## String
### chr(number)
Returns ASCII character from a number. This function complements [`ord()`](#functions:ord).
* **Parameters:**
**number** (*number*) – the ASCII number
* **Returns:**
ASCII character
* **Return type:**
string
### ord(character)
Return ASCII value of a character. This function complements [`chr()`](#functions:chr).
* **Parameters:**
**character** (*string*) – the ASCII character
* **Returns:**
the ASCII value
* **Return type:**
number
### str_repeat(string, multiplier)
Returns the string repeated multiplier times.
* **Parameters:**
* **string** (*string*) – the input string
* **multiplier** (*number*) – the string multiplier
* **Returns:**
the repeated string
* **Return type:**
string
> **See also**
>
> It’s significantly faster to use the string repeat `*` operator since it’s an operator and not a function.
### str_replace(search, replace, subject)
Returns the string subject with the string search replace with replace.
* **Parameters:**
* **search** (*string*) – the search string
* **replace** (*string*) – the replace string
* **subject** (*string*) – the string acted upon
* **Returns:**
subject with searched replaced with replace
* **Return type:**
string
```hsl
str_replace("Hello", "Hej", "Hello World") // "Hej World"
```
### str_split(string, delimiter[, limit = 0])
Splits the string into an array on the delimiter.
* **Parameters:**
* **string** (*string*) – the string
* **delimiter** (*string*) – the delimiter
* **limit** (*number*) – the maximum number of parts returned
* **Returns:**
an array of strings
* **Return type:**
array
```hsl
str_split("how are you", " ", 2) // ["how","are you"]
str_split("how are you", " ", -2) // ["how are","you"]
```
> **See also**
>
> To join an array to a string, see [`array_join()`](#functions:array_join).
### str_find(string, substring[, offset = 0])
Return the position (starting from zero) of the first occurrence of substring in the string (starting from the offset). If the substring is **not** found -1 is returned.
* **Parameters:**
* **string** (*string*) – the input string
* **substring** (*string*) – the string to look for
* **offset** (*number*) – the offset from the start
* **Returns:**
the position where substring is found or -1
* **Return type:**
number
### str_rfind(string, substring[, offset])
Return the position (starting from zero) of the last occurrence of substring in the string, searching backward.
If offset is given, only occurrences starting at or before position offset (counted from the beginning of the string) are considered.
If omitted, the whole string is searched. If the substring is not found, -1 is returned.
* **Parameters:**
* **string** (*string*) – the input string
* **substring** (*string*) – the string to look for
* **offset** (*number*) – the offset from the start
* **Returns:**
the position where substring is found or -1
* **Return type:**
number
### str_lower(string)
Returns string with all US-ASCII characters lowercased.
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string lowercased
* **Return type:**
string
### str_upper(string)
Returns string with all US-ASCII characters uppercased.
* **Parameters:**
**string** (*string*) – the input string
* **Returns:**
the string uppercased
* **Return type:**
string
### str_slice(string, offset[, length])
Return the substring of string.
* **Parameters:**
* **string** (*string*) – the input string
* **offset** (*number*) – the start position
* **length** (*number*) – the length limit if given
* **Returns:**
the substring
* **Return type:**
string
> **See also**
>
> It’s significantly faster to use the slice `[:]` operator since it’s an operator and not a function.
### str_strip(string[, characters])
Returns string with whitespace strip characters (`\s\t\r\n`) removed from the start and end of the string.
* **Parameters:**
* **string** (*string*) – the input string
* **characters** (*string*) – the strip characters
* **Returns:**
the trimmed string
* **Return type:**
string
### str_lstrip(string[, characters])
Returns string with whitespace strip characters (`\s\t\r\n`) removed from the start of the string.
* **Parameters:**
* **string** (*string*) – the input string
* **characters** (*string*) – the strip characters
* **Returns:**
the trimmed string
* **Return type:**
string
### str_rstrip(string[, characters])
Returns string with whitespace strip characters (`\s\t\r\n`) removed from the end of the string.
* **Parameters:**
* **string** (*string*) – the input string
* **characters** (*string*) – the strip characters
* **Returns:**
the trimmed string
* **Return type:**
string
### str_chunk(string, length[, firstlength])
Return the string chunks into an array of strings.
* **Parameters:**
* **string** (*string*) – the input string
* **length** (*number*) – the length of chunks
* **firstlength** (*number*) – the length of the first chunk
* **Returns:**
the chunked string
* **Return type:**
array of string
```hsl
str_chunk("xxxxxxxxx", 3) // ["xxx","xxx","xxx"]
str_chunk("xxxxxxxxx", 3, 1) // ["x","xxx","xxx", "xx"]
```
## Regular expression
### pcre_match(pattern, subject)
PCRE matching in subject.
* **Parameters:**
* **pattern** ([Regex](#literals:regextype) or string) – the regular expression
* **subject** (*string*) – the string to match against
* **Returns:**
returns matches, if no result is found an empty array is returned.
* **Return type:**
array
Perl compatible regular expression data matching and extraction, requires capture groups. All modifiers supported by `=~` operator are available.
> **Note**
>
> Use [raw strings](#literals:rawstring) so you don’t have to escape the pattern.
> **See also**
>
> For matching only the [regular expression](#operators:regex) operator can be used.
### pcre_match_all(pattern, subject)
The implementation is identical to [`pcre_match()`](#functions:pcre_match) except the return type.
* **Parameters:**
* **pattern** ([Regex](#literals:regextype) or string) – the regular expression
* **subject** (*string*) – the string to match against
* **Returns:**
returns multiple results group by capture groups, and matched result.
* **Return type:**
array
### pcre_quote(string)
Quote all metacharacters which has special meaning in a regular expression.
* **Parameters:**
**string** (*string*) – the string
* **Returns:**
a quoted string
* **Return type:**
string
### pcre_compile(string)
Pre-compile a regex to a regular expression object.
* **Parameters:**
**string** (*string*) – the regular expression
* **Returns:**
a regex object
* **Return type:**
[Regex](#literals:regextype)
### pcre_replace(pattern, replace, subject[, limit = 0])
Perl compatible regular expression data matching and replacing.
* **Parameters:**
* **pattern** ([Regex](#literals:regextype) or string) – the regular expression to match
* **replace** (*function* *or* *string*) – the pattern to replace as string or a callback function
* **subject** (*string*) – the string acted upon
* **limit** (*number*) – max occurrences to replace (*0* equals *unlimited*)
* **Returns:**
return subject with the replacements done
* **Return type:**
string
In *replace* matches are available using `$0` to `$n`. `$0` will be the entire match, and `$1` (and forward) each match group.
The replace function should take one argument (array of values `[$0, $n...]`) and return a string value.
```hsl
echo pcre_replace("\\[link](.*?)\\[/link]",
"$1",
"[link]http://halon.se[/link]");
// http://halon.se
echo pcre_replace("\\d", "($0)", "foo1bar2baz");
// foo(1)bar(2)baz
// "ucfirst()"
echo pcre_replace(''\b[a-z]'', function ($i) { return str_upper($i[0]); }, "hello world");
// Hello World
```
## Socket
### *class* Socket
This class allows POSIX like socket(2) code. A socket resource is created for each Socket instance, this resource is automatically garbage collected (closed) once the object is destroyed.
#### constructor(family, type)
* **Parameters:**
* **family** (*string*) – address family either `AF_INET`, `AF_INET6` or `AF_UNIX`
* **type** (*string*) – socket type either `SOCK_STREAM` (TCP) or `SOCK_DGRAM` (UDP)
```hsl
$socket = Socket("AF_INET", "SOCK_STREAM");
$socket->close();
$socket2 = Socket(Socket::AF($address), "SOCK_STREAM");
$socket2->close();
```
#### bind(address[, port[, options]])
Bind the socket to *address* and *port*. The address must match the Sockets address family. On error `none` is returned.
* **Parameters:**
* **address** (*string*) – address to bind
* **port** (*number*) – port to bind
* **options** (*array*) – options array
* **Returns:**
this
* **Return type:**
[`Socket`](#functions:Socket) or none
The following options are available in the options array.
> * **nonlocal** (boolean) Allow binding of a nonlocal source address (BINDANY). The default is `false`.
#### close()
Close the socket and destroy the internal socket resource. On error `none` is returned.
* **Returns:**
this
* **Return type:**
[`Socket`](#functions:Socket) or none
> **Note**
>
> Sockets are automatically garbage collected (closed). However you may want to explicitly call close.
#### connect(address[, port])
Connect the socket to *address* and *port*. The address must match the Sockets address family. On error `none` is returned.
* **Parameters:**
* **address** (*string*) – address to connect to
* **port** (*number*) – port to connect to
* **Returns:**
this
* **Return type:**
[`Socket`](#functions:Socket) or none
#### errno()
Get the latest errno returned from the underlying POSIX socket API.
* **Returns:**
errno
* **Return type:**
number
#### recv(length[, flags])
Receive data on socket. On error `none` is returned.
* **Parameters:**
* **length** (*number*) – up to length bytes to receive (max 65536)
* **flags** (*array*) – flags to control the behaviour
* **Returns:**
data
* **Return type:**
string or none
Flags may be any of, the default is no POSIX recv(3) flag. If multiple flags are given in the array, they are bitwise OR-ed.
| Name | Behaviour |
|--------------|--------------------------------|
| MSG_PEEK | peek at incoming message |
| MSG_WAITALL | wait for full request or error |
| MSG_DONTWAIT | do not block |
#### send(data)
Send data on socket. On error `none` is returned.
* **Parameters:**
**data** (*string*) – data to send
* **Returns:**
bytes sent
* **Return type:**
number or none
#### settimeout(timeout)
Set the timeout for socket operations.
* **Parameters:**
**timeout** (*number*) – timeout in seconds. The default is no timeout.
* **Returns:**
this
* **Return type:**
[`Socket`](#functions:Socket)
#### shutdown(how)
Shutdown the socket for receiving, sending or both. On error `none` is returned.
* **Parameters:**
**how** (*string*) – how to shutdown either `SHUT_RD`, `SHUT_WR` or `SHUT_RDWR`.
* **Returns:**
this
* **Return type:**
[`Socket`](#functions:Socket) or none
> **Note**
>
> Sockets are automatically closed.
#### toFFIValue()
Return the file descriptor representing the current socket. On error `none` is returned.
* **Returns:**
An FFIValue of sint32 type
* **Return type:**
[`FFIValue`](#functions:FFIValue) or none
> **Note**
>
> The returned file descriptor must not be closed.
#### *static* AF(address)
Return the AF family of an address (either `AF_INET` or `AF_INET6`). A utility function helpful when constructing a [`Socket`](#functions:Socket) class. On error `none` is returned.
* **Parameters:**
**address** (*string*) – address
* **Returns:**
AF family
* **Return type:**
string or none
#### *static* Resource(socket)
Return a Socket object from a SocketResource.
* **Parameters:**
**socket** (*SocketResource*) – a SocketResource
* **Returns:**
A Socket resource
* **Return type:**
[`Socket`](#functions:Socket) or none
### *class* TLSSocket
This class allows OpenSSL like SSL(3) code. The TLSSocket class takes a connected [`Socket`](#functions:Socket) instance (SOCK_STREAM) and encapsulates any read and writes in TLS/SSL.
#### constructor(socket, options)
* **Parameters:**
* **socket** ([*Socket*](#functions:Socket)) – a socket
* **options** (*array*) – options array
The following options are available in the options array.
> * **tls_protocols** (string) Use one or many of the following TLS protocols; `SSLv2`, `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2` or `TLSv1.3`. Protocols may be separated by `,` and excluded by `!`. The default is `!SSLv2,!SSLv3`.
> * **tls_ciphers** (string) List of ciphers to support. The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_ciphersuites** (string) List of TLS ciphersuites to support (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_groups** (string) List of TLS groups to offer for TLS key exchange. (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_verify_name** (array) Hostnames to verify against the certificate’s CN and SAN (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **tls_verify_ca** (boolean) Verify certificate against known CAs. The default is `false`.
> * **tls_sni** (string) Request a certificate using the SNI extension. The default is not to use SNI.
> * **tls_client_cert** (array or string) If given as an associative array it should include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. If given as a string use the following id of a certificate in the configuration as client certificate. The default is to not send a client certificate.
> **Note**
>
> By default, no certificate nor hostname validation is done.
#### handshake()
Perform the TLS/SSL handshake. If the handshake fails or the validation fails `none` is returned.
* **Returns:**
this
* **Return type:**
[`TLSSocket`](#functions:TLSSocket) or none
#### recv(length)
Receive data on TLS/SSL socket. This function may perform an implicit handshake. On error `none` is returned.
* **Parameters:**
**length** (*number*) – up to length bytes to receive (max 65536)
* **Returns:**
data
* **Return type:**
string or none
#### send(data)
Send data on TLS/SSL socket. This function may perform an implicit handshake. On error `none` is returned.
* **Parameters:**
**data** (*string*) – data to send
* **Returns:**
bytes sent
* **Return type:**
number or none
#### shutdown()
Shut down the TLS/SSL connection. This function may need to be called multiple times. See SSL_shutdown(3) for details. On error `none` is returned.
* **Returns:**
shutdown status
* **Return type:**
number or none
#### errno()
Get the latest errno returned from the underlying OpenSSL SSL(3) socket API.
* **Returns:**
errno
* **Return type:**
number
#### getpeerx509()
Get the peer certificate (X.509) as a [`X509`](#functions:X509) instance. On error `none` is returned.
* **Returns:**
The peer certificate
* **Return type:**
[`X509`](#functions:X509) or none
#### toSocket()
Return a [`Socket`](#functions:Socket) class for the current TLSSocket object (upcast).
* **Returns:**
A Socket class for the current TLSSocket object.
* **Return type:**
[`Socket`](#functions:Socket)
### *class* X509
This class allows you to parse an X509 resource. The X509 class takes a *X509Resource*.
#### constructor(x509resource)
* **Parameters:**
**x509resource** (*X509Resource*) – a X509Resource
#### subject()
The subject of the certificate. The first field in the tuple is the name (eg. CN, OU) and the second is the value.
* **Returns:**
The subject
* **Return type:**
array of [string, string]
#### issuer()
The issuer of the certificate. The first field in the tuple is the name (eg. CN, OU) and the second is the value.
* **Returns:**
The issuer
* **Return type:**
array of [string, string]
#### subject_alt_name()
The subject alt names (DNS) items
* **Returns:**
The SAN
* **Return type:**
array
#### version()
The version of the X.509 certificate
* **Returns:**
The version
* **Return type:**
number
#### serial_number()
The serial number in HEX
* **Returns:**
The serial
* **Return type:**
string
#### not_valid_before()
The start date of the certificate (in unix time)
* **Returns:**
The certificate start date
* **Return type:**
number
#### not_valid_after()
The end date of the certificate (in unix time)
* **Returns:**
The certificate end date
* **Return type:**
number
#### public_key([options])
Export the public key in binary DER format (default) or in PEM format.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
The public key
* **Return type:**
string
The following options are available in the options array.
> * **pem** (boolean) Export the public key in PEM format. The default is `false`.
#### verify(calist[, intermediates[, revokelist]])
Verify the certificate against a list of CA’s (with optional intermediate certificates).
* **Parameters:**
* **calist** (*array*) – array of X509
* **intermediates** (*array*) – array of X509
* **revokelist** (*array*) – array of X509CRL
* **Returns:**
The verification result
* **Return type:**
array
The return value is an associative arrays with `result` (boolean) or `error` (string), `errno` (number) and `depth` (number). For details see OpenSSL’s `X509_verify_cert` and `X509_STORE_CTX_get_error`.
```hsl
if ($c->verify($calist)["result"])
echo "ok";
```
#### chain(calist[, intermediates[, options]])
Resolve a chain from the X509 to the calist root (if not none), with the longest intermediates chain that can be built.
* **Parameters:**
* **calist** (*array*) – array of X509 or none
* **intermediates** (*array*) – array of X509
* **options** (*array*) – options array
* **Returns:**
The chain
* **Return type:**
array
The following options are available in the options array.
> * **selfsigned** (boolean) Include selfsigned certificate in the chain (often the CA trust). The default is `false`.
The return value is an associative arrays with either `error` (string) or an array `result` of X509 certificates.
```hsl
if ($c->chain($calist)["result"])
echo "ok";
```
#### export([options])
Export the certificate in binary DER format (default) or in PEM format.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
The certificate
* **Return type:**
string
The following options are available in the options array.
> * **pem** (boolean) Export the X.509 in PEM format. The default is `false`.
```hsl
// SHA256 fingerprint
echo sha2($c->export(), 256);
```
#### extensions([options])
Return all extensions in the certificate.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
List of extensions
* **Return type:**
array
The following options are available in the options array.
> * **text** (boolean) Decode known extensions to friendly text. The default is `false`.
The return value is an array of associative arrays with `oid`, `name`, `critical`, `value` and optionally `text`.
```hsl
echo $c->extensions(["text" => true]); // [0=>["oid"=>"2.5.29.19","name"=>"basicConstraints","critical"=>false,"value"=>"...","text"=>"CA:FALSE"]]
```
#### toResource()
Return the objects X509 resource.
* **Returns:**
The internal X509 resource
* **Return type:**
`X509Resource`
#### toFFIValue()
Return a pointer to a [OpenSSL X509](https://www.openssl.org/docs/man1.1.1/man7/x509.html) object representing the current X509 resource.
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue)
> **Note**
>
> The X509 pointer must not be deleted.
>
> ```c
> #include
>
> void processx509(X509* x509) {
> }
> ```
#### *static* String(data[, options])
Return a X509 resource containing the data.
* **Parameters:**
* **data** (*string*) – the content
* **options** (*array*) – options array
* **Returns:**
A X509 resource
* **Return type:**
[`X509`](#functions:X509)
The following options are available in the options array.
> * **pem** (boolean) The X.509 in PEM format. The default is `false` (DER).
> * **multiple** (boolean) Import multiple X509 certificates as an array. This option changes the return value to an array of X509 resources. The default is `false`.
### is_x509_resource(resource)
Return a true if resource is a X509 resource.
* **Parameters:**
**resource** (*any*) – the resource
* **Returns:**
if the argument is X509 resource
* **Return type:**
boolean
### *class* X509CRL
This class allows you to parse an X509 Certificate Revocation List resource. The X509CRL class takes a *X509CRLResource*.
#### constructor(x509CRLresource)
* **Parameters:**
**x509CRLresource** (*X509CRLResource*) – a X509CRLResource
#### issuer()
The issuer of the CRL. The first field in the tuple is the name (eg. CN, OU) and the second is the value.
* **Returns:**
The issuer
* **Return type:**
array of [string, string]
#### revoked()
Get the list of revoked CRL, the `serial_number` and `reason`.
* **Returns:**
The revoked certificates
* **Return type:**
array of array
#### version()
The version of the X.509 CRL
* **Returns:**
The version
* **Return type:**
number
#### last_update()
The last update date of the CRL (in unix time)
* **Returns:**
The last update date
* **Return type:**
number
#### next_update()
The next update date of the CRL (in unix time)
* **Returns:**
The next update date
* **Return type:**
number
#### export([options])
Export the CRL in binary DER format (default) or in PEM format.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
The CRL
* **Return type:**
string
The following options are available in the options array.
> * **pem** (boolean) Export the X.509 CRL in PEM format. The default is `false`.
```hsl
// SHA256 fingerprint
echo sha2($c->export(), 256);
```
#### extensions([options])
Return all extensions in the CRL.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
List of extensions
* **Return type:**
array
The following options are available in the options array.
> * **text** (boolean) Decode known extensions to friendly text. The default is `false`.
The return value is an array of associative arrays with `oid`, `name`, `critical`, `value` and optionally `text`.
```hsl
echo $c->extensions(["text" => true]); // [0=>["oid"=>"2.5.29.19","name"=>"basicConstraints","critical"=>false,"value"=>"...","text"=>"CA:FALSE"]]
```
#### toResource()
Return the objects X509CRL resource.
* **Returns:**
The internal X509CRL resource
* **Return type:**
`X509CRLResource`
#### *static* String(data[, options])
Return a X509CRL resource containing the data.
* **Parameters:**
* **data** (*string*) – the content
* **options** (*array*) – options array
* **Returns:**
A X509CRL resource
* **Return type:**
[`X509CRL`](#functions:X509CRL)
The following options are available in the options array.
> * **pem** (boolean) The X.509 CRL in PEM format. The default is `false` (DER).
> * **multiple** (boolean) Import multiple X509 CRL as an array. This option changes the return value to an array of X509CRL resources. The default is `false`.
### is_x509crl_resource(resource)
Return a true if resource is a X509 resource.
* **Parameters:**
**resource** (*any*) – the resource
* **Returns:**
if the argument is X509 resource
* **Return type:**
boolean
## FFI
The foreign function interface (FFI) enables loading of shared libraries following C interface calling conventions. The FFI interface has its own types (C types) and memory. It’s very easy to crash the Halon script engine if not used properly. The FFI feature is not enabled by default and needs to be enabled in the .yaml configuration for each program.
### *class* FFI
This class allows you to load a shared object/library.
#### constructor(path)
* **Parameters:**
**path** (*string*) – a library (eg. libc.so.7)
```hsl
$libc = FFI("libc.so.7");
```
#### func(name, arguments, returntype)
The name of the function to load, use [`FFI.type()`](#functions:FFI.type) to define the correct function signature. If the function is not found, `none` is returned.
* **Parameters:**
* **name** (*string*) – the function name
* **arguments** (*FFIType*) – the list of argument types
* **returntype** (*FFIType*) – the return type
* **Returns:**
A function object
* **Return type:**
[`FFIFunction`](#functions:FFIFunction) or none
```hsl
$malloc = $libc->func("malloc", [ FFI::type("uint64") ], FFI::type("pointer"));
$free = $libc->func("free", [ FFI::type("pointer") ], FFI::type("void"));
$printf = $libc->func("printf", [ FFI::type("pointer"), FFI::type("...") ], FFI::type("sint32")); // variadic
```
The `...` type prepresents functions having a variadic arguments list. All values in the variadic arguments list must have an explicit type since they are unknown in the function definition.
#### symbol(name)
Return a pointer to a global symbol in the library (eg. a variable). This function is the equivalent of dlsym(2). If the symbol is not found, `none` is returned.
* **Parameters:**
**name** (*string*) – a symbol name
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue) or none
#### *static* type(name)
A factory function for FFI types. This function is usually used to declare the function signature of an [`FFIFunction`](#functions:FFIFunction).
* **Parameters:**
**name** (*string*) – a type name
* **Returns:**
An FFI type
* **Return type:**
[`FFIType`](#functions:FFIType)
The following types are available.
* `void` (can only be used as return value)
* `uint8`, `sint8`, `uint16`, `sint16`, `uint32`, `sint32`, `uint64`, `sint64`
* `float`, `double`
* `pointer`
* `...` (can only be used as function argument)
#### *static* cnumber(type, number)
Create an [`FFIValue`](#functions:FFIValue) containing a C number. It’s a basic type, which exists for the lifetime of the returned value and passed by value.
* **Parameters:**
* **type** (*FFIType*) – an FFI C number type
* **number** (*number*) – a number
* **Returns:**
An FFI value
* **Return type:**
[`FFIValue`](#functions:FFIValue)
The following FFI number types are available:
* `uint8`, `sint8`, `uint16`, `sint16`, `uint32`, `sint32`, `uint64`, `sint64`, `float`, `double`
```hsl
$malloc = $libc->func("malloc", [ FFI::type("uint64") ], FFI::type("pointer"));
$ptr = $malloc(FFI::cnumber(FFI::type("uint64"), 32));
```
#### *static* cstring(value)
Allocate a null-terminated C string (`char *`) in memory from a HSL string and return an [`FFIValue`](#functions:FFIValue) of `pointer` type pointing to that memory. This memory is owned by the [`FFIValue`](#functions:FFIValue) resource (use the [`FFI.detach()`](#functions:FFI.detach) function to disclaim ownership). This function is intentionally not binary safe.
* **Parameters:**
**value** (*string*) – a string
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue)
```hsl
$fopen = $libc->func("fopen", [ FFI::type("pointer"), FFI::type("pointer") ], FFI::type("pointer"));
$fp = $fopen(FFI::cstring("/dev/zero"), FFI::cstring("r"));
```
#### *static* callback(arguments, returntype, callback)
Allocate a FFI callback function and return a [`FFIValue`](#functions:FFIValue) of `pointer` type pointing to that function. This pointer can be passed a pointer to a FFI function taking a C function pointer. This memory is owned by the [`FFIValue`](#functions:FFIValue) resource (use the [`FFI.detach()`](#functions:FFI.detach) function to disclaim ownership). The callback function argument will be FFI types and the return type needs to be of FFI type as well.
* **Parameters:**
* **arguments** (*FFIType*) – the list of argument types
* **returntype** (*FFIType*) – the return type
* **callback** (*function*) – a callback function
* **Returns:**
An FFIValue of pointer type pointing to a C function callback
* **Return type:**
[`FFIValue`](#functions:FFIValue)
```hsl
$math = $lib->func("math", [ FFI::type("pointer"), FFI::type("sint32"), FFI::type("sint32") ], FFI::type("sint32"));
$cb = FFI::callback([ FFI::type("sint32"), FFI::type("sint32") ], FFI::type("sint32"), function ($a, $b) {
return FFI::cnumber(FFI::type("sint32"), FFI::number($a) * FFI::number($b));
});
echo FFI::number($math($cb, 10, 5));
```
For the corresponding C library function
```c
typedef int (*mathcb)(int, int);
extern "C" int math(mathcb cb, int a, int b) {
return cb(a, b);
}
```
#### *static* nullptr()
Create an [`FFIValue`](#functions:FFIValue) containing a NULL pointer.
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue)
> **Note**
>
> The C equivalent of this function is `NULL`.
#### *static* allocate(size)
Allocate memory of *size* in bytes and return an [`FFIValue`](#functions:FFIValue) of `pointer` type pointing to that memory. This memory is owned by the [`FFIValue`](#functions:FFIValue) resource (use the [`FFI.detach()`](#functions:FFI.detach) function to disclaim ownership). The memory is initially filled with zeros.
* **Parameters:**
**size** (*any*) – the memory size in bytes
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue)
> **Note**
>
> The C equivalent of this function is `malloc(size)` with a `memset(pointer, size, 0)`.
#### *static* memcpy(pointer, data)
Copy the binary content of (string) data into memory location pointed to by an [`FFIValue`](#functions:FFIValue) of `pointer` type. The caller must make sure the pointer location is of sufficient length.
* **Parameters:**
* **pointer** (*FFIValue*) – an FFIValue of pointer type
* **data** (*string*) – the data to copied
* **Returns:**
An FFIValue
* **Return type:**
[`FFIValue`](#functions:FFIValue)
> **Note**
>
> The C equivalent of this function is `memcpy(pointer, data, datalen)`.
#### *static* byref(value)
Return an [`FFIValue`](#functions:FFIValue) of `pointer` type pointing to the [`FFIValue`](#functions:FFIValue) *value*.
* **Parameters:**
**value** (*FFIValue*) – an FFI value
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue)
> **Note**
>
> The C equivalent of this function is `&value`.
#### *static* deref(value[, type])
Return an [`FFIValue`](#functions:FFIValue) with [`FFIType`](#functions:FFIType) of *type* with the value at the address pointed at by the [`FFIValue`](#functions:FFIValue) value. The default type is `pointer`. If the type is a pointer and dereferenced pointer points to NULL then `none` is returned.
* **Parameters:**
* **value** (*FFIValue*) – an FFI value
* **type** (*FFIType*) – an FFI type
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue) or none
> **Note**
>
> The C equivalent of this function is `*value`.
#### *static* offset(pointer, offset)
Return a new [`FFIValue`](#functions:FFIValue) of `pointer` type pointing the same memory with an offset.
* **Parameters:**
* **pointer** (*FFIValue*) – an FFI value of pointer type
* **offset** (*number*) – the offset in bytes
* **Returns:**
An FFIValue of pointer type
* **Return type:**
[`FFIValue`](#functions:FFIValue)
> **Note**
>
> The C equivalent of this function is `pointer + 32`.
#### *static* string(pointer[, size])
Copy the binary content of a memory location pointed to by an [`FFIValue`](#functions:FFIValue) of `pointer` type to a HSL string. If the size is omitted the memory will be copied up to the first NULL character as a null-terminated C string (`char *`).
* **Parameters:**
* **pointer** (*FFIValue*) – an FFI value of pointer type
* **size** (*number*) – bytes to copy
* **Returns:**
A binary safe string
* **Return type:**
string
#### *static* number(value)
Convert an FFI value to a HSL number. The number type can safely represent all integers between *+/-9007199254740991* (the equivalent of `(2 ** 53) - 1`). If you expect to work with greater numbers use [`FFI.number64()`](#functions:FFI.number64).
* **Parameters:**
**value** (*FFIValue*) – an FFI value
* **Returns:**
A number
* **Return type:**
number
#### *static* number64(value)
Convert an FFI value (`uint64`, `sint64` or `pointer`) to a pair of two 32 bit integers ([high, low]). For signed negative numbers a two complement representation is used.
* **Parameters:**
**value** (*FFIValue*) – an FFI value
* **Returns:**
A number pair
* **Return type:**
array of number
#### *static* attach(pointer[, destructor])
Assign the ownership of the data pointed to by the pointer argument ([`FFIValue`](#functions:FFIValue) of `pointer` type). The default destructor is *free*. An optional destructor [`FFIFunction`](#functions:FFIFunction) (should have one `pointer` argument) may be given.
* **Parameters:**
* **pointer** (*FFIValue*) – an FFIValue of pointer type
* **destructor** (*FFIFunction*) – a destructor function
* **Returns:**
The pointer argument
* **Return type:**
[`FFIValue`](#functions:FFIValue)
```hsl
$fopen = $libc->func("fopen", [ FFI::type("pointer"), FFI::type("pointer") ], FFI::type("pointer"));
$fclose = $libc->func("fclose", [ FFI::type("pointer") ], FFI::type("void"));
$fp = FFI::attach($fopen->call("/dev/zero", "r"), $fclose);
```
#### *static* detach(pointer)
Remove the ownership of the data pointed to by the pointer argument ([`FFIValue`](#functions:FFIValue) of `pointer` type).
* **Parameters:**
**pointer** (*FFIValue*) – an FFI value of pointer type
* **Returns:**
The pointer argument
* **Return type:**
[`FFIValue`](#functions:FFIValue)
### FFIFunction(...args)
A callable Function object of type FFIFunction.
* **Parameters:**
**args** (*FFIValue*) – FFIValues or a HSL value
* **Returns:**
Return value of function call
* **Return type:**
[`FFIValue`](#functions:FFIValue) or none (for `void` or a `pointer` returning *NULL*)
Implicit conversion can be made if the function signature has once of the following types and the argument HSL type match. Note that the lifetime of converted values are for the duration of the function call.
> | Declaration type | HSL type | Conversion |
> |--------------------|------------|----------------------------------------------------------------|
> | `pointer` | String | [`FFI.cstring()`](#functions:FFI.cstring) |
> | `pointer` | None | [`FFI.nullptr()`](#functions:FFI.nullptr) |
> | *any number* | Number | Be causes of value truncations |
> | `...` | FFIValue | Argument must be of explicit type |
```hsl
$fopen = $libc->func("fopen", [ FFI::type("pointer"), FFI::type("pointer") ], FFI::type("pointer"));
$fp1 = $fopen(FFI::cstring("/dev/zero"), FFI::cstring("r"));
$fp2 = $fopen("/dev/zero", "r"); // implicit conversion
$printf("%s %zu\n", FFI::cstring("hello"), FFI::cnumber(FFI::type("uint64"), 123));
```
### FFIType
An FFIType resource holds information about a function signature.
The following types are available.
> * `void` (can only be used as return value)
> * `uint8`, `sint8`, `uint16`, `sint16`, `uint32`, `sint32`, `uint64`, `sint64`
> * `float`, `double`
> * `pointer`
> * `...` (can only be used as function argument)
### FFIValue
An FFIValue resource is a container for an FFI value (it also contains the FFIType) so that the correct conversions can be made. If the value is of a pointer type you may control the lifetime of the object using the [`FFI.attach()`](#functions:FFI.attach) and [`FFI.detach()`](#functions:FFI.detach) functions.
## Shared memory
The memory function API provides shared, atomic and synchronized memory access between parallel script executions within the same program. The memory resides in runtime memory (heap) hence it is automatically cleared upon program restart. Access to this memory is also available through the protobuf control socket API. The memory functions share the same memory and lock as the [barrier](#structures:barrier) structure.
### memory_add(key, value)
Adds the value to the store, but only if the key doesn’t already exist.
* **Parameters:**
* **key** (*string*) – the key name
* **value** (*any*) – the value
* **Returns:**
if the value was added
* **Return type:**
boolean
### memory_dec(key[, offset=1, remove_if_zero=false])
Decrements a number value in the store. If the key doesn’t exist, it’s created and the value is initialized to 0 before the decrement. If the value is not a number, this function fail and will return *none*.
* **Parameters:**
* **key** (*string*) – the key name
* **offset** (*number*) – the offset to decrement
* **remove_if_zero** (*boolean*) – remove key if number reaches zero
* **Returns:**
the decremeted value
* **Return type:**
number or none
### memory_delete(key[, callback])
Deletes a key from the store. If the key existed, the callback (if provided) will be called with the key and value as arguments.
* **Parameters:**
* **key** (*string*) – the key name
* **callback** (*function*) – a callback function
* **Returns:**
if the key was removed
* **Return type:**
boolean
### memory_exists(key[, callback])
Checks if a key in the store exists. If the key exists the callback (if provided) will be called with the key and value as arguments.
* **Parameters:**
* **key** (*string*) – the key name
* **callback** (*function*) – a callback function
* **Returns:**
if the key exists
* **Return type:**
boolean
### memory_fetch(key[, callback])
Returns the value of a key in the store. If the entry doesn’t exist and no callback is provided, `none` will be returned. If the entry doesn’t exist and a callback is provided, it will be called with the key as argument, and the return value of the callback will be used as return value for this function.
* **Parameters:**
* **key** (*string*) – the key name
* **callback** (*function*) – a callback function
* **Returns:**
the value of key
* **Return type:**
any
```hsl
// fetch or create+fetch
echo memory_fetch("x", function ($k) {
$v = 123;
memory_store("x", $v);
return $v;
});
```
> **Note**
>
> Since this function may return `none` if the key doesn’t exist, it’s not possible to differentiate between keys with the value `none` and a non-existing key. If it’s important to differentiate between those, use [`memory_exists()`](#functions:memory_exists) with a callback.
### memory_inc(key[, offset=1])
Increments a number value in the store. If the key doesn’t exist, it’s created and the value is initialized to 0 before the increment. If the value is not a number, this function fail and will return *none*.
* **Parameters:**
* **key** (*string*) – the key name
* **offset** (*number*) – the offset to increment
* **Returns:**
the incremeted value
* **Return type:**
number or none
### memory_store(key, value[, callback])
Update or create the value of key in the store. If the key exists, the callback (if provided) will be called with the key and the current value as arguments in order to know what value was overwritten.
* **Parameters:**
* **key** (*string*) – the key name
* **value** (*any*) – the value
* **callback** (*function*) – a callback function
* **Returns:**
if the key exists
* **Return type:**
boolean
### memory_update(key, value, callback[, initial])
Update the value of key in the store, if it exists. The return value of the callback function (called with the key, current value and value as argument) will be used as the stored value.
If the key doesn’t exist, and…
> * …no initial value is provided, no action will take place.
> * …an initial value is provided, the callback will be called with the initial value as the current value.
* **Parameters:**
* **key** (*string*) – the key name
* **value** (*any*) – the value
* **callback** (*function*) – a callback function
* **initial** (*any*) – an initial value
* **Returns:**
if the key exists
* **Return type:**
boolean
```hsl
// append items to an array (create the array if it doesn't exist)
memory_update("list", "item", function ($k, $c, $v) {
$c[] = $v;
return $c;
}, []);
// compare-and-swap (CAS) example between 3 and 5
$swapped = false;
if (memory_update("x", 5, function ($k, $c, $v) closure ($swapped) {
if ($c != 3) return $c;
$swapped = true;
return $v;
}) and $swapped)
{
echo "swapped";
}
```
## Server
### server_concurrency(serverid[, address])
Return the current server concurrency for a specific IP or network.
* **Parameters:**
* **serverid** (*string*) – the serverid name
* **address** (*string*) – the address (may be IP or a network)
* **Returns:**
concurrency if successful
* **Return type:**
number or none
```hsl
// Block if concurrency > 5
$concurrency = server_concurrency($arguments["serverid"], $arguments["remoteip"]);
if (is_number($concurrency) and $concurrency > 5)
Reject("Too many concurrent connections");
```
## Queue
### queue_suspend(fields, ttl[, options])
Add a dynamic queue pickup suspend for the following fields, this will cause no more messages matching this condition to be picked up for delivery during the ttl of the suspend. If a matching suspend already exists only the TTL is updated. On error `none` is returned.
* **Parameters:**
* **fields** (*array*) – fields array
* **ttl** (*number*) – ttl in seconds
* **options** (*array*) – options array
* **Return type:**
array
The following fields are available in the fields array.
> * **transportid** (string) Transport ID
> * **localip** (string) Local IP
> * **remoteip** (string) Remote IP
> * **remotemx** (string) Remote MX
> * **recipientdomain** (string) Recipient domain
> * **tenantid** (string) Tenant ID
> * **jobid** (string) Job ID
> * **grouping** (string) Grouping
The following options are available in the options array.
> * **tag** (string) Apply tag to suspend.
> * **properties** (array) List of properties to be added (associative string-string array)
> * **update** (boolean) If we should update the suspend. The default is `true`.
> * **id** (string) Set the UUID for the suspend (instead of auto-generate). If the UUID exists the policy is updated.
The return value is always an associative array with `id`.
```hsl
// Suspend matching traffic for one minute
queue_suspend(
["remotemx" => "*.protection.example.com"],
60
);
```
### queue_policy(fields, condition, policy, ttl[, options])
Add a dynamic queue pickup policy for the specific fields, matching a specific condition. If a matching policy (fields and condition) already exists the condition and TTL is updated. On error `none` is returned.
* **Parameters:**
* **fields** (*array*) – fields array
* **condition** (*array*) – condition array
* **policy** (*array*) – policy array
* **ttl** (*number*) – ttl in seconds
* **options** (*array*) – options array
* **Return type:**
array
The following field values are available in the fields array. The counter for those fields needs to exists in the policy configuration before it can have dynamic condition added to it.
> * `transportid`
> * `localip`
> * `remoteip`
> * `remotemx`
> * `recipientdomain`
> * `tenantid`
> * `jobid`
> * `grouping`
The following condition are available in the condition array.
> * **transportid** (string) Transport ID
> * **localip** (string) Local IP
> * **remoteip** (string) Remote IP
> * **remotemx** (string) Remote MX
> * **recipientdomain** (string) Recipient domain
> * **tenantid** (string) Tenant ID
> * **jobid** (string) Job ID
> * **grouping** (string) Grouping
The following policies are available in the policy array.
> * **concurrency** (number) The concurrency limit.
> * **rate** (array) The rate given as [messages, interval]. The interval is given in seconds.
> * **connectinterval** The connect interval that should be enforced, specified in seconds.
> * **properties** (array) An associative array of properties
> * **tag** (string) Tag if concurrency or rate applies.
> * **stop** (boolean) If stop-on-match should be enabled. The default is `false`.
> * **cluster** (boolean) If clustering should be enabled. The default is `true`.
The following options are available in the options array.
* **update** (boolean) If we should update the policy. The default is `true`.
* **id** (string) Set the UUID for the policy (instead of auto-generate). If the UUID exists the policy is updated.
* **type** (string) Set the policy type (`backoff`, `warmup`). The default is `dynamic`.
The return value is always an associative array with `id`.
```hsl
// Enforce a 10 messages per minute limit for an hour
queue_policy(
["localip", "recipientdomain"],
["recipientdomain" => "example.com"],
["rate" => [10, 60]],
3600
);
```
> **Note**
>
> If you have groupings and you add a condition matching a grouping, then the condition will be applied on the grouping and not the individual item. For example if you have a grouping named `#exampleRollup` (`*.example.com`) and you add a condition for `mx1.example.com` the condition will be applied on the `#exampleRollup` grouping instead.
### queue_policy_delete(id)
Remove the queue policy reference by the id. Return a boolean value if the policy was removed. On error `none` is returned.
* **Parameters:**
**id** (*string*) – a queue policy id
* **Return type:**
boolean or none
### queue_quota(name)
Get the usage for a specific quota. Messages may be assigned to one or more quotas when calling eg. [`eod.EODMailMessage.queue()`](#eod:eod.EODMailMessage.queue) which is then incremented accordingly. When the messages is removed from the queue (delivered or not) the quota is decremented.
* **Parameters:**
**name** (*string*) – the quota name
* **Returns:**
the quota information
* **Return type:**
array
The return value is always an associative array with `count` and `bytes`.
```hsl
// Enforce a limit of 1 GiB of message data in queue for a specific senderdomain
// In this example we have a made-up namespace called "senderdomain:"
// but that is not necessary
//
// Do not forget to specificying the same quota name when accepting messages
//
if (queue_quota("senderdomain:$senderdomain")["bytes"] > 1024 * 1024 * 1024)
Defer("Too many mail in queue from $senderdomain");
```
## Metrics
### metrics_increment(name, labels[, value])
Increment a metric of type gauge or counter.
* **Parameters:**
* **name** (*string*) – the metric name
* **labels** (*array*) – the labels (may be empty)
* **value** (*number*) – the increment value (default 1)
* **Returns:**
true if successful
* **Return type:**
boolean or none
### metrics_decrement(name, labels[, value])
Decrement a metric of type gauge.
* **Parameters:**
* **name** (*string*) – the metric name
* **labels** (*array*) – the labels (may be empty)
* **value** (*number*) – the decrement value (default 1)
* **Returns:**
true if successful
* **Return type:**
boolean or none
### metrics_set(name, labels, value)
Set a metric of type gauge, counter or histogram.
* **Parameters:**
* **name** (*string*) – the metric name
* **labels** (*array*) – the labels (may be empty)
* **value** (*number*) – the increment value (default 1)
* **Returns:**
true if successful
* **Return type:**
boolean or none
### metrics_get(name, labels)
Get a metric of type gauge, counter or histogram.
* **Parameters:**
* **name** (*string*) – the metric name
* **labels** (*array*) – the labels (may be empty)
* **Returns:**
an associative array with `value` and optionally `buckets`, none if not found
* **Return type:**
array or none
## Classes
### class Exception
This builtin class is used to create Exception objects. Similar to the built in exceptions [thrown](#structures:throw).
#### constructor(message)
Create an instance of the Exception class.
* **Parameters:**
**message** (*string*) – the exception message
```hsl
try {
throw Exception("Error");
} catch ($e) {
echo $e->toString();
}
```
#### toString()
Return a user-friendly represention of the exception message including file, line or column if available.
* **Returns:**
exception message
* **Return type:**
string
#### message()
Return the error message without any additional information regarding file, line or column.
* **Returns:**
exception message
* **Return type:**
string
#### file()
Return the file which caused the exception. This function will only return the file for builin exceptions.
* **Returns:**
file
* **Return type:**
string or none
#### line()
Return the line which caused the exception. This function will only return the line for builin exceptions.
* **Returns:**
line number
* **Return type:**
number or none
#### column()
Return the column which caused the exception. This function will only return the column for builin exceptions.
* **Returns:**
column number
* **Return type:**
number or none
# SMTP server
There are multiple extensions to the language, which provides context specific variables and functions. The *smtpd* process implements the [connect](#connect), [proxy](#proxy), [HELO](#helo), [AUTH](#auth), [MAIL FROM](#mailfrom), [RCPT TO](#rcptto), [end-of-DATA](#eod) and [disconnect](#disconnect) context. These scripts operates on an SMTP connection. The `$transaction["id"]` variable is set when connecting and may be regenerated upon the client sending a RSET command. There is also a `$context` variable which is bound to a connection and may be used in any script, which is useful for passing data (only string, number, boolean, null and array types are supported) between phases.
```default
.--------------------------------. <-- connect context
< | 220 example.org ESMTP |
> | HELO example.com | <-- HELO context
< | 250 OK |
| ... |
> | AUTH LOGIN | <-- AUTH context
| ... |
< | 250 OK |
> | MAIL FROM: | <-- MAIL FROM context
< | 250 OK |
> | RCPT TO: | <-- RCPT TO context
< | 250 OK |
> | DATA |
< | 354 Feed me |
> | Subject: Lunch |
> | |
> | Lunch on friday? |
> | . | <-- end-of-DATA context
< | 250 Accepted |
> | QUIT |
< | 250 Bye |
`--------------------------------´ <-- disconnect context
|
,-------> queue
| | <-- Pre-delivery context
| delivery attempt
| | <-- Post-delivery context
\__________/ \______ done
```
* [Connect](#connect)
* [Variables](#connect:variables)
* [Arguments](#connect:arguments)
* [Connection](#connect:connection)
* [Transaction](#connect:transaction)
* [Functions](#connect:functions)
* [`Accept()`](#connect:Accept)
* [`Reject()`](#connect:Reject)
* [`Defer()`](#connect:Defer)
* [On script error](#connect:on-script-error)
* [On implicit termination](#connect:on-implicit-termination)
* [Proxy](#proxy)
* [Variables](#proxy:variables)
* [Arguments](#proxy:arguments)
* [Connection](#proxy:connection)
* [HELO](#proxy:helo)
* [TLS](#proxy:tls)
* [Peercert](#proxy:peercert)
* [AUTH](#proxy:auth)
* [Transaction](#proxy:transaction)
* [Recipient](#proxy:recipient)
* [Address](#proxy:address)
* [Functions](#proxy:functions)
* [`Pass()`](#proxy:Pass)
* [`Reply()`](#proxy:Reply)
* [`Next()`](#proxy:Next)
* [On script error](#proxy:on-script-error)
* [On implicit termination](#proxy:on-implicit-termination)
* [HELO](#helo)
* [Variables](#helo:variables)
* [Arguments](#helo:arguments)
* [Connection](#helo:connection)
* [TLS](#helo:tls)
* [Peercert](#helo:peercert)
* [AUTH](#helo:auth)
* [Transaction](#helo:transaction)
* [Functions](#helo:functions)
* [`Accept()`](#helo:Accept)
* [`Reject()`](#helo:Reject)
* [`Defer()`](#helo:Defer)
* [On script error](#helo:on-script-error)
* [On implicit termination](#helo:on-implicit-termination)
* [AUTH](#auth)
* [Variables](#auth:variables)
* [Arguments](#auth:arguments)
* [Connection](#auth:connection)
* [HELO](#auth:helo)
* [TLS](#auth:tls)
* [Peercert](#auth:peercert)
* [Transaction](#auth:transaction)
* [Functions](#auth:functions)
* [`Accept()`](#auth:Accept)
* [`Reject()`](#auth:Reject)
* [`Defer()`](#auth:Defer)
* [`Reply()`](#auth:Reply)
* [On script error](#auth:on-script-error)
* [On implicit termination](#auth:on-implicit-termination)
* [Authentication diagram](#auth:authentication-diagram)
* [MAIL FROM](#mailfrom)
* [Variables](#mailfrom:variables)
* [Arguments](#mailfrom:arguments)
* [Connection](#mailfrom:connection)
* [HELO](#mailfrom:helo)
* [TLS](#mailfrom:tls)
* [Peercert](#mailfrom:peercert)
* [AUTH](#mailfrom:auth)
* [Transaction](#mailfrom:transaction)
* [Address](#mailfrom:address)
* [Functions](#mailfrom:functions)
* [`Accept()`](#mailfrom:Accept)
* [`Reject()`](#mailfrom:Reject)
* [`Defer()`](#mailfrom:Defer)
* [On script error](#mailfrom:on-script-error)
* [On implicit termination](#mailfrom:on-implicit-termination)
* [RCPT TO](#rcptto)
* [Variables](#rcptto:variables)
* [Arguments](#rcptto:arguments)
* [Connection](#rcptto:connection)
* [HELO](#rcptto:helo)
* [TLS](#rcptto:tls)
* [Peercert](#rcptto:peercert)
* [AUTH](#rcptto:auth)
* [Transaction](#rcptto:transaction)
* [Recipient](#rcptto:recipient)
* [Address](#rcptto:address)
* [Functions](#rcptto:functions)
* [`Accept()`](#rcptto:Accept)
* [`Reject()`](#rcptto:Reject)
* [`Defer()`](#rcptto:Defer)
* [On script error](#rcptto:on-script-error)
* [On implicit termination](#rcptto:on-implicit-termination)
* [End-of-DATA](#eod)
* [Variables](#eod:variables)
* [Arguments](#eod:arguments)
* [Connection](#eod:connection)
* [HELO](#eod:helo)
* [TLS](#eod:tls)
* [Peercert](#eod:peercert)
* [AUTH](#eod:auth)
* [Transaction](#eod:transaction)
* [Recipient](#eod:recipient)
* [Address](#eod:address)
* [http](#eod:http)
* [Functions](#eod:functions)
* [Actions](#eod:actions)
* [`Accept()`](#eod:Accept)
* [`Reject()`](#eod:Reject)
* [`Defer()`](#eod:Defer)
* [DATA, MIME and attachments](#eod:data-mime-and-attachments)
* [`EODMailMessage`](#eod:_CPPv414EODMailMessage)
* [`EODMailMessage.queue()`](#eod:EODMailMessage.queue)
* [On script error](#eod:on-script-error)
* [On implicit termination](#eod:on-implicit-termination)
* [Disconnect](#disconnect)
* [Variables](#disconnect:variables)
* [Arguments](#disconnect:arguments)
* [Error](#disconnect:error)
* [Reply](#disconnect:reply)
* [Connection](#disconnect:connection)
* [HELO](#disconnect:helo)
* [TLS](#disconnect:tls)
* [Peercert](#disconnect:peercert)
* [AUTH](#disconnect:auth)
* [Functions](#disconnect:functions)
* [`Pass()`](#disconnect:Pass)
* [On script error](#disconnect:on-script-error)
* [On implicit termination](#disconnect:on-implicit-termination)
# Connect
This script is executed before the SMTP banner is sent.
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#connect:v-a1) | array | yes | Context/hook arguments |
| [$connection](#connect:v-u1) | array | yes | Connection/session bound |
| [$transaction](#connect:v-t1) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|-----------------|--------|--------------------------------------------|----------------------------------------------------------------------------------|
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteptrresult | array | `["reverse" => [...], "forward" => [...]]` | DNS result for Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| proxyip | string | `"192.168.1.1"` | IP address of the proxy server (not always available) |
| proxytlv | string | `fffffffffffffffffffffff...` | Any additional PROXY protocol TLV received (not always available, in hex format) |
### Connection
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
### Transaction
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-----------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
## Functions
### Accept([options])
Allow the connection to be established.
Optionally change the `remoteip` and PTR of the accepted client connection, which is written back to the `$connection` variable.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **remoteip** (string) Change the IP address of the accepted client connection.
> * **remoteptr** (string) Set the reverse DNS pointer (PTR) for the IP address.
> * **serverhost** (string) Set the SMTP servers hostname.
> * **reason** (string or array) The greeting banner response.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `220` and *enhanced* is not used.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
> * **maxmessagesize** (number) Change max message size for this connection (in bytes).
> * **tls** (array) The associative array must contain `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*.
> **Note**
>
> The `remoteptr` option can be useful for eg. decoding IPv4 addresses embedded in an IPv6 address ([RFC6052](https://tools.ietf.org/html/rfc6052)).
>
> ```hsl
> $x = unpack("N*", inet_pton($arguments["remoteip"]));
> if (length($x) == 4 and $x[0:3] == [6619035, 0, 0]) // 64:ff9b::[IPv4]
> $ip = inet_ntop(pack("N", $x[3]));
> ```
### Reject([reason[, options]])
Close the connection with a permanent (521) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – reject message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `521` and *enhanced* is not used.
> * **tls** (array) The associative array must contain `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. For `Reject()` calls this is only used for implicit TLS.
### Defer([reason[, options]])
Close the connection with a temporary (421) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – defer message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `421` and *enhanced* is not used
> * **tls** (array) The associative array must contain `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. For `Defer()` calls this is only used for implicit TLS.
## On script error
On script error [`Defer()`](#connect:connect.Defer) is called.
## On implicit termination
If not explicitly terminated then [`Accept()`](#connect:connect.Accept) is called.
# Proxy
This advanced and powerful script can be executed before an SMTP command is sent to the internal state machine of the SMTP server (hence before the command is processed by the SMTP server). It can be used to build custom SMTP commands or modify the current command line before processing.
## Variables
These are the read-only arguments available for each command. Depending on when the proxy script is executed, the different objects may contain different information.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#proxy:v-a8) | array | yes | Context/hook arguments |
| [$connection](#proxy:v-c8) | array | yes | Connection/session bound |
| [$transaction](#proxy:v-t8) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|--------------|--------|--------------------------|------------------------------|
| command | string | `"XCLIENT ADDR=1.1.1.1"` | The SMTP command line issued |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [helo](#proxy:he8) | array | | HELO information (not always available) |
| [tls](#proxy:tls8) | array | | TLS information (if TLS was started) |
| [auth](#proxy:au8) | array | | AUTH information (not always available) |
#### HELO
| Array item | Type | Example | Description |
|--------------|--------|----------------------|----------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#proxy:p8) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
#### AUTH
| Array item | Type | Example | Description |
|--------------|--------|--------------|--------------------------------------|
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| username | string | `"mailuser"` | SASL username (not always available) |
### Transaction
| Array item | Type | Example | Description |
|--------------------------------------------------------------|--------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
| sender | string | `"test@example.org"` | Sender address (envelope), lowercase |
| [senderaddress](#proxy:a8) | array | `["localpart" => "examp...]` | Sender address (envelope) |
| senderparams | array | `["SIZE" => "2048", ... ]` | Sender parameters to the envelope address (keys will always be in uppercase) |
| recipients | array | `[["recipient" => "test@example.com", ...], ...]` | List of all accepted [recipients](#proxy:v-t-r8) (envelope), in order of scanning |
#### Recipient
| Array item | Type | Example | Description |
|--------------------------------------------------------|--------|------------------------------|---------------------------------------------------------------------------------|
| recipient | string | `"test@example.com"` | Recipient address (envelope), lowercase |
| [address](#proxy:a8) | array | `["localpart" => "examp...]` | Recipient address (envelope) |
| params | array | `["NOTIFY" => "NEVER", .. ]` | Recipient parameters to the envelope address (keys will always be in uppercase) |
| transportid | string | `"inbound"` | Transport ID for recipient |
#### Address
| Array item | Type | Example | Description |
|--------------|--------|-----------------|------------------------|
| localpart | string | `"test"` | Local part of address |
| domain | string | `"example.org"` | Domain part of address |
## Functions
### Pass([options])
Pass the command to the SMTP server’s state machine.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **command** (string) Change the SMTP command.
> * **next** (boolean) Request to get the next command as well. The default is `false`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Reply([reason[, options]])
Send a reply to the client (The default is code 250). The command is not passed to the SMTP server’s state machine.
* **Parameters:**
* **reason** (*string* *or* *array*) – the message to reply
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default is pre-defined.
> * **next** (boolean) Request to get the next command as well. The default is `false`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Next([options])
Request to get the next line from the client to the proxy hook, without sending any data back to the client.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
## On script error
On script error [`Reply()`](#proxy:proxy.Reply) is called with a generic 421 response.
## On implicit termination
If not explicitly terminated then [`Pass()`](#proxy:proxy.Pass) is called.
# HELO
This script is executed on `HELO` and `EHLO`. It allows verification of identification.
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#helo:v-a2) | array | yes | Context/hook arguments |
| [$connection](#helo:v-c2) | array | yes | Connection/session bound |
| [$transaction](#helo:v-t2) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|--------------------------------------------------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname of sender |
| extensions | array | `["PIPELINING", "SIZE 1...` | The extensions to be sent to the client (if EHLO was issued) |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [tls](#helo:tls2) | array | | TLS information (if TLS was started) |
| [auth](#helo:au2) | array | | AUTH information (not always available) |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#helo:p7) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
#### AUTH
| Array item | Type | Example | Description |
|--------------|--------|--------------|--------------------------------------|
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| username | string | `"mailuser"` | SASL username (not always available) |
### Transaction
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-----------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
## Functions
### Accept([options])
Accept the *HELO* or *EHLO* command. Optionally change the `host` of the sending client, which is written back to the `$connection` variable.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **extensions** (array) SMTP service extensions to announce in EHLO responses.
> * **host** (string) Change the client HELO hostname for the current connection.
> * **reason** (string) First line of the response. The default is the servers hostname.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `250` and *enhanced* is not used.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Reject([reason[, options]])
Reject the *HELO* or *EHLO* command with a permanent (554) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – reject message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `554` and *enhanced* is not used.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Defer([reason[, options]])
Defer the *HELO* or *EHLO* command with a temporary (450) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – defer message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `450` and *enhanced* is not used.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
## On script error
On script error [`Defer()`](#helo:helo.Defer) is called.
## On implicit termination
If not explicitly terminated then [`Accept()`](#helo:helo.Accept) is called.
# AUTH
The AUTH script allows trusted SMTP clients to authenticate. The SASL mechanisms *LOGIN* and *PLAIN* are implemented in the SMTP engine and will populate the `username` and `password` arguments. If you add support for custom authentication mechanisms you will need to use the `mechanism`, `state` and `response` arguments instead.
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#auth:v-a3) | array | yes | Context/hook arguments |
| [$connection](#auth:v-c3) | array | yes | Connection/session bound |
| [$transaction](#auth:v-t3) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|--------------|--------|--------------|----------------------------------------------------------------------|
| username | string | `"mailuser"` | SASL username (only available with LOGIN or PLAIN) |
| password | string | `"secret"` | SASL password (only available with LOGIN or PLAIN) |
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| state | number | `0` | SASL state, incremeted per Reply (not available with LOGIN or PLAIN) |
| response | string | `none` | SASL response (not available with LOGIN or PLAIN) |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [helo](#auth:he3) | array | | HELO information (not always available) |
| [tls](#auth:tls3) | array | | TLS information (if TLS was started) |
#### HELO
| Array item | Type | Example | Description |
|--------------|--------|----------------------|----------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#auth:p3) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
### Transaction
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-----------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
## Functions
### Accept([options])
Authorize the login request.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **username** (string) Set or change the username. The default is the `username` argument (if available).
> * **reason** (string or array) The reason to report. The default is a system generated message.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `235` and *enhanced* is `[2,7,0]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Reject([reason[, options]])
Reject the login request with a permanent (535) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – reject message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `535` and *enhanced* is `[5,7,8]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Defer([reason[, options]])
Defer the login request with a temporary (454) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – defer message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `454` and *enhanced* is `[4,7,0]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Reply([reply[, options]])
Send a reply (334) message. The reply will be base64 encoded before sent to the client. This function is used to implement custom authentication mechanisms.
* **Parameters:**
* **reply** (*string*) – the reply message
* **options** (*array*) – an options array
* **Increments:**
`state` argument
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `334` and *enhanced* is not used.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
## On script error
On script error [`Defer()`](#auth:auth.Defer) is called.
## On implicit termination
If not explicitly terminated then [`Reject()`](#auth:auth.Reject) is called.
## Authentication diagram
A flow chart diagram of how custom authentication is implemented:
```default
+--------------+
| AUTH request |
+--------------+
|
|
v
+---------------------------------------+
| state = 0 |
+---------------------------------------+ Accept() +-------------------+
| > AUTH mechanism [response] | ---- Reject() ---> | AUTH request done |
+---------------------------------------+ Defer() +-------------------+
| ^
| |
Reply() <------------------+ |
| | |
| | |
v | |
+---------------------------------------+ | |
| state += 1 | | |
+---------------------------------------+ | |
| > response | ---+----+
+---------------------------------------+ |
| |
| |
+------------------------+
```
# MAIL FROM
The `MAIL FROM` script allows verification of the sender.
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#mailfrom:v-a4) | array | yes | Context/hook arguments |
| [$connection](#mailfrom:v-c4) | array | yes | Connection/session bound |
| [$transaction](#mailfrom:v-t4) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|--------------------------------------------------------|--------|------------------------------|------------------------------------------------------------------------------|
| sender | string | `"test@example.org"` | Sender address (envelope), lowercase |
| [address](#mailfrom:a4) | array | `["localpart" => "test"...]` | Sender address (envelope) |
| params | array | `["SIZE" => "2048", ... ]` | Sender parameters to the envelope address (keys will always be in uppercase) |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [helo](#mailfrom:he4) | array | | HELO information (not always available) |
| [tls](#mailfrom:tls4) | array | | TLS information (if TLS was started) |
| [auth](#mailfrom:au4) | array | | AUTH information (not always available) |
#### HELO
| Array item | Type | Example | Description |
|--------------|--------|----------------------|----------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#mailfrom:p4) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
#### AUTH
| Array item | Type | Example | Description |
|--------------|--------|--------------|--------------------------------------|
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| username | string | `"mailuser"` | SASL username (not always available) |
### Transaction
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-----------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
#### Address
| Array item | Type | Example | Description |
|--------------|--------|-----------------|------------------------|
| localpart | string | `"test"` | Local part of address |
| domain | string | `"example.org"` | Domain part of address |
## Functions
### Accept([options])
Accept the *MAIL FROM* command (sender).
Optionally change the sender accepted, which is written back to `$transaction`.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **sender** (string or array) Change the sender email address, either as a string or an associative array with a `localpart` and `domain`. The default is the `address` argument.
> * **params** (array) Change the sender parameters. The default is the `params` argument.
> * **reason** (string or array) The reason to report. The default is a system generated message.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `250` and *enhanced* is `[2,1,0]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
> **Note**
>
> This function changes the sender for all recipients. To change sender per recipient change the sender in the [EOD](#eod) context when queuing a recipient.
### Reject([reason[, options]])
Reject the *MAIL FROM* command (sender) with a permanent (554) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – reject message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `550` and *enhanced* is `[5,7,1]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Defer([reason[, options]])
Defer the *MAIL FROM* command (sender) with a temporary (450) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – defer message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The array may contain *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `450` and *enhanced* is `[4,7,1]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
## On script error
On script error [`Defer()`](#mailfrom:mailfrom.Defer) is called.
## On implicit termination
If not explicitly terminated then [`Accept()`](#mailfrom:mailfrom.Accept) is called.
# RCPT TO
The `RCPT TO` script allows verification of recipients.
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#rcptto:v-a5) | array | yes | Context/hook arguments |
| [$connection](#rcptto:v-c5) | array | yes | Connection/session bound |
| [$transaction](#rcptto:v-t5) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|--------------------------------------------------------|--------|------------------------------|---------------------------------------------------------------------------------|
| recipient | string | `"test@example.com"` | Recipient address (envelope), lowercase |
| [address](#rcptto:a5) | array | `["localpart" => "test"...]` | Recipient address (envelope) |
| params | array | `["NOTIFY" => "NEVER", .. ]` | Recipient parameters to the envelope address (keys will always be in uppercase) |
| transportid | string | `"inbound"` | ID of the transport profile to be used |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [helo](#rcptto:he5) | array | | HELO information (not always available) |
| [tls](#rcptto:tls5) | array | | TLS information (if TLS was started) |
| [auth](#rcptto:au5) | array | | AUTH information (not always available) |
#### HELO
| Array item | Type | Example | Description |
|--------------|--------|----------------------|----------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#rcptto:p5) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
#### AUTH
| Array item | Type | Example | Description |
|--------------|--------|--------------|--------------------------------------|
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| username | string | `"mailuser"` | SASL username (not always available) |
### Transaction
| Array item | Type | Example | Description |
|--------------------------------------------------------------|--------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
| sender | string | `"test@example.org"` | Sender address (envelope), lowercase |
| [senderaddress](#rcptto:a5) | array | `["localpart" => "test"...]` | Sender address (envelope) |
| senderparams | array | `["SIZE" => "2048", ... ]` | Sender parameters to the envelope address (keys will always be in uppercase) |
| recipients | array | `[["recipient" => "test@example.com", ...], ...]` | List of all accepted [recipients](#rcptto:v-t-r5) (envelope), in order of scanning |
#### Recipient
| Array item | Type | Example | Description |
|--------------------------------------------------------|--------|------------------------------|---------------------------------------------------------------------------------|
| recipient | string | `"test@example.com"` | Recipient address (envelope), lowercase |
| [address](#rcptto:a5) | array | `["localpart" => "test"...]` | Recipient address (envelope) |
| params | array | `["NOTIFY" => "NEVER", .. ]` | Recipient parameters to the envelope address (keys will always be in uppercase) |
| transportid | string | `"inbound"` | Transport ID for recipient |
#### Address
| Array item | Type | Example | Description |
|--------------|--------|-----------------|------------------------|
| localpart | string | `"test"` | Local part of address |
| domain | string | `"example.org"` | Domain part of address |
## Functions
### Accept([options])
Accept the *RCPT TO* command (recipient).
Optionally change the recipient accepted and its transport, which is written back to `$transaction` for subsequent executions.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **recipient** (string or array) Change the recipient email address, either as a string or an associative array with a `localpart` and `domain`. The default is the `address` argument.
> * **params** (array) Change the recipient parameters. The default is the `params` argument.
> * **transportid** (string) Change the transport ID. The default is the `transportid` argument.
> * **reason** (string or array) The reason to report. The default is a system generated message.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `250` and *enhanced* is `[2,1,5]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Reject([reason[, options]])
Reject the *RCPT TO* command (recipient) with a permanent (554) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – reject message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `554` and *enhanced* is `[5,7,1]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### Defer([reason[, options]])
Defer the *RCPT TO* command (recipient) with a temporary (450) error.
* **Parameters:**
* **reason** (*string* *or* *array*) – defer message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `450` and *enhanced* is `[4,7,1]`.
> * **priority** (number) Change the connection priority (0 normal, 1 high).
## On script error
On script error [`Defer()`](#rcptto:rcptto.Defer) is called.
## On implicit termination
If not explicitly terminated then [`Accept()`](#rcptto:rcptto.Accept) is called.
# End-of-DATA
The end-of-DATA script is executed once, when the message is fully received (but not yet accepted).
To relay the message for all recipients, call [`EODMailMessage.queue()`](#eod:eod.EODMailMessage.queue) for each `$transaction["recipients"]` and then [`Accept()`](#eod:eod.Accept).
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#eod:v-a6) | array | yes | Context/hook arguments |
| [$connection](#eod:v-c6) | array | yes | Connection/session bound |
| [$transaction](#eod:v-t6) | array | yes | Transaction bound |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|-----------------------------------------------------|-----------------------------------------------------------------|-----------|---------------------------------------------------------|
| mail | [`EODMailMessage`](#eod:_CPPv414EODMailMessage) | | An instance of the mail message |
| metadata | any | | User-defined data (only available through the HTTP API) |
| [http](#eod:v-h) | array | | HTTP data (only available through the HTTP API) |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [helo](#eod:he6) | array | | HELO information (not always available) |
| [tls](#eod:tls6) | array | | TLS information (if TLS was started) |
| [auth](#eod:au6) | array | | AUTH information (not always available) |
#### HELO
| Array item | Type | Example | Description |
|--------------|--------|----------------------|----------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#eod:p6) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
#### AUTH
| Array item | Type | Example | Description |
|--------------|--------|--------------|--------------------------------------|
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| username | string | `"mailuser"` | SASL username (not always available) |
### Transaction
| Array item | Type | Example | Description |
|--------------------------------------------------------------|--------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
| ts | number | `1575558785.1234` | Unix time of transaction |
| sender | string | `"test@example.org"` | Sender address (envelope), lowercase |
| [senderaddress](#eod:a6) | array | `["localpart" => "test"...]` | Sender address (envelope) |
| senderparams | array | `["SIZE" => "2048", ... ]` | Sender parameters to the envelope address (keys will always be in uppercase) |
| recipients | array | `[["recipient" => "test@example.com", ...], ...]` | List of all accepted [recipients](#eod:v-t-r6) (envelope), in order of scanning |
#### Recipient
| Array item | Type | Example | Description |
|--------------------------------------------------------|--------|------------------------------|---------------------------------------------------------------------------------|
| recipient | string | `"test@example.com"` | Recipient address (envelope), lowercase |
| [address](#eod:a6) | array | `["localpart" => "test"...]` | Recipient address (envelope) |
| params | array | `["NOTIFY" => "NEVER", .. ]` | Recipient parameters to the envelope address (keys will always be in uppercase) |
| transportid | string | `"inbound"` | Transport ID for recipient |
#### Address
| Array item | Type | Example | Description |
|--------------|--------|-----------------|------------------------|
| localpart | string | `"test"` | Local part of address |
| domain | string | `"example.org"` | Domain part of address |
#### http
| Array item | Type | Example | Description |
|--------------|--------|------------------------------|--------------------------------------------|
| headers | array | `[["user-agent", "..."],...` | HTTP request headers (names are lowercase) |
## Functions
* **Actions** [`Accept()`](#eod:eod.Accept) [`Reject()`](#eod:eod.Reject) [`Defer()`](#eod:eod.Defer)
* **DATA, MIME and attachments** [`EODMailMessage`](#eod:_CPPv414EODMailMessage) [`MIMEPart`](#functions:_CPPv48MIMEPart)
### Actions
#### Accept([options])
Accept the *DATA* command (mail data).
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **reason** (string or array) The reason to report. The default is a system generated message.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `250` and *enhanced* is `[2,0,0]`.
> * **metadata** (any) User-defined data (only available through the HTTP API)
> * **priority** (number) Change the connection priority (0 normal, 1 high).
#### Reject([reason[, options]])
Reject (550) a message.
* **Parameters:**
* **reason** (*string* *or* *array*) – reject message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `550` and *enhanced* is `[5,7,1]`.
> * **metadata** (any) User-defined data (only available through the HTTP API)
> * **priority** (number) Change the connection priority (0 normal, 1 high).
#### Defer([reason[, options]])
Defer (421) a message.
* **Parameters:**
* **reason** (*string* *or* *array*) – defer message with reason
* **options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **disconnect** (boolean) Disconnect the client. The default is `false`.
> * **reply_codes** (array) The associative array may contain the properties *code* (number) and *enhanced* (array of three numbers). To remove the *enhanced* code set it to `[0,0,0]`. The default *code* is `421` and *enhanced* is `[4,3,2]`.
> * **metadata** (any) User-defined data (only available through the HTTP API)
> * **priority** (number) Change the connection priority (0 normal, 1 high).
### DATA, MIME and attachments
#### class EODMailMessage : [MailMessage](#functions:_CPPv411MailMessage)
In the end-of-DATA context the [`MailMessage`](#functions:_CPPv411MailMessage) class has been extended with one additional function; [`EODMailMessage.queue()`](#eod:eod.EODMailMessage.queue).
##### queue(sender, recipient, transportid[, options])
Queue the message.
* **Parameters:**
* **sender** (*string* *or* *array*) – the sender email address, either as a string or an associative array with a `localpart` and `domain`. The domain support internationalized domain names (U-labels).
* **recipient** (*string* *or* *array*) – the recipient email address, either as a string or an associative array with a `localpart` and `domain`. The domain support internationalized domain names (U-labels).
* **transportid** (*string*) – the transport profile ID
* **options** (*array*) – an options array
* **Returns:**
an id object (with `transaction` and `queue`)
* **Return type:**
array
The following options are available in the options array.
> * **metadata** (array) Add metadata to the queued message, as a key-value pair array of strings.
> * **hold** (boolean) Put the message in the hold (inactive) queue. The default is `false`.
> * **tenantid** (string) Assign a tenantid the message.
> * **jobid** (string) Assign a jobid the message.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **fsync** (bool) Override default fsync behaviour for this specific message (for multiple recipients, true has precedence).
> * **quotas** (array) An array of quotas to be associated with the message, for use with [`queue_quota()`](#functions:queue_quota).
> * **delay** (number) Delay the first delivery attempt, in seconds. The default is `0`.
> * **dsn** (array) Associative array of `envid` (string), `ret` (string), `notify` (string) and `orcpt` (string).
## On script error
On script error [`Defer()`](#eod:eod.Defer) is called.
## On implicit termination
If not explicitly terminated then [`Defer()`](#eod:eod.Defer) is called and no recipients will be queued.
# Disconnect
This script is executed when the client has disconnected.
## Variables
These are the pre-defined variables available for each command. Depending on when the disconnect script is executed, the different objects may contain different information.
| Variable | Type | Read-only | Description |
|------------------------------------------------------------|--------|-------------|----------------------------------------------|
| [$arguments](#disconnect:v-xc9) | array | yes | Context/hook arguments |
| [$connection](#disconnect:v-c9) | array | yes | Connection/session bound |
| $transaction | array | yes | Transaction bound (empty) |
| $context | any | no | Connection bound user-defined (default none) |
### Arguments
| Array item | Type | Example | Description |
|------------------------------------------------------|--------|------------------------------------------|------------------------------------------------------|
| close | string | `"local"` | Who initialized the disconnect (`local` or `remote`) |
| [reply](#disconnect:ac9r) | array | `["code" => 250, "enhanced" => [1, ...]` | A SMTP protocol response (if available) |
| [error](#disconnect:ac9e) | array | `["reason" => "....` | If an error occurred (if available) |
#### Error
| Array item | Type | Example | Description |
|--------------|--------|------------------------------|--------------------------------------------------------------------------------------|
| reason | string | `"Connection reset by peer"` | The error message |
| type | string | `"socket"` | The type of error (if avilable, `socket` or `tls`) |
| code | number | `54` | The error code (for `socket` it’s an errno and for `tls` it’s an OpenSSL error code) |
#### Reply
| Array item | Type | Example | Description |
|----------------------------------------------------------|--------|-------------|-----------------------------------------------------------------------|
| code | number | `221` | A SMTP status code |
| enhanced | array | `[2, 5, 0]` | A SMTP enhanced status code |
| reason | array | `["Bye"]` | A SMTP response text |
| [state](#postdelivery:as1) | string | `"QUIT"` | An enum to indicate which issued SMTP command triggerd the disconnect |
### Connection
| Array item | Type | Example | Description |
|-----------------------------------------------------|--------|-----------------------------|----------------------------------------------------------|
| id | string | `"18c190a3-93f-47d7-bd..."` | ID of the connection |
| priority | number | `1` | The connection priority |
| remoteip | string | `"192.168.1.11"` | IP address of the connected client |
| remoteport | number | `41666` | TCP port of connected client |
| remoteptr | string | `"mail.example.org"` | Reverse DNS (FCrDNS) for remoteip (not always available) |
| remoteuid | number | `106` | UNIX socket remote user id (not always available) |
| localip | string | `"10.0.0.1"` | IP address of the server |
| localport | number | `25` | TCP port of the server |
| serverid | string | `"inbound"` | ID of the server |
| [helo](#disconnect:he9) | array | | HELO information (not always available) |
| [tls](#disconnect:tls9) | array | | TLS information (if TLS was started) |
| [auth](#disconnect:au9) | array | | AUTH information (not always available) |
#### HELO
| Array item | Type | Example | Description |
|--------------|--------|----------------------|----------------------|
| verb | string | `"EHLO"` | HELO or EHLO command |
| host | string | `"mail.example.com"` | HELO hostname |
#### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-----------------------------|--------------------------------------------------|
| protocol | string | `"TLSv1.3"` | The protocol |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher |
| keysize | number | `256` | The keysize |
| sni | string | `"example.com"` | The SNI hostname (not always available) |
| [peercert](#disconnect:p9) | array | | The peer certificate (if provided by the client) |
##### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
#### AUTH
| Array item | Type | Example | Description |
|--------------|--------|--------------|--------------------------------------|
| mechanism | string | `"PLAIN"` | SASL mechanism (always in uppercase) |
| username | string | `"mailuser"` | SASL username (not always available) |
## Functions
### Pass()
Terminate the disconnect script.
* **Returns:**
doesn’t return, script is terminated
## On script error
On script error [`Pass()`](#disconnect:disconnect.Pass) is called.
## On implicit termination
If not explicitly terminated then [`Pass()`](#disconnect:disconnect.Pass) is called.
# SMTP queue
The *smtpd* process implements the [pre-](#predelivery) and [post-delivery](#postdelivery) contexts. These contexts operates on a message in queue. A message in queue is not directly bound to an inbound SMTP connection, hence its delivery is not done inline.
```default
,-------> queue
| | <-- Pre-delivery context
| delivery attempt
| | <-- Post-delivery context
\__________/ \______ done
```
* [Pre-delivery](#predelivery)
* [Variables](#predelivery:variables)
* [Arguments](#predelivery:arguments)
* [Queue](#predelivery:queue)
* [Plugin](#predelivery:plugin)
* [Message](#predelivery:message)
* [Id](#predelivery:id1)
* [Address](#predelivery:address)
* [File](#predelivery:file)
* [DSN](#predelivery:dsn)
* [Functions](#predelivery:functions)
* [`Try()`](#predelivery:Try)
* [`Queue()`](#predelivery:Queue)
* [`Bounce()`](#predelivery:Bounce)
* [`Delete()`](#predelivery:Delete)
* [`SetMetaData()`](#predelivery:SetMetaData)
* [`GetMetaData()`](#predelivery:GetMetaData)
* [On script error](#predelivery:on-script-error)
* [On implicit termination](#predelivery:on-implicit-termination)
* [Post-delivery](#postdelivery)
* [Variables](#postdelivery:variables)
* [Arguments](#postdelivery:arguments)
* [Policy](#postdelivery:policy)
* [Policies](#postdelivery:policies)
* [Attempt](#postdelivery:attempt)
* [Result](#postdelivery:result)
* [Error](#postdelivery:error)
* [Connection](#postdelivery:connection)
* [TLS](#postdelivery:tls)
* [Peercert](#postdelivery:peercert)
* [TLSA](#postdelivery:tlsa)
* [Records](#postdelivery:records)
* [Plugin](#postdelivery:plugin)
* [DSN](#postdelivery:dsn)
* [Queue](#postdelivery:queue)
* [Plugin](#postdelivery:ai1p)
* [Message](#postdelivery:message)
* [Id](#postdelivery:id2)
* [Address](#postdelivery:address)
* [File](#postdelivery:file)
* [DSN](#postdelivery:dsn2)
* [Functions](#postdelivery:functions)
* [`Queue()`](#postdelivery:Queue)
* [`Bounce()`](#postdelivery:Bounce)
* [`Delete()`](#postdelivery:Delete)
* [`Default()`](#postdelivery:Default)
* [`SetMetaData()`](#postdelivery:SetMetaData)
* [`GetMetaData()`](#postdelivery:GetMetaData)
* [On script error](#postdelivery:on-script-error)
* [On implicit termination](#postdelivery:on-implicit-termination)
* [References](#postdelivery:references)
* [SMTP states](#postdelivery:smtp-states)
# Pre-delivery
The pre-delivery script is executed just before an email is being put into the active queue
(for delivery as soon as the active queue policies allow).
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-----------------------------------------------------------|--------|-------------|-----------------------------------------------------------------------------------|
| [$arguments](#predelivery:v-p1) | array | yes | Context/hook arguments |
| [$message](#predelivery:v-m2) | array | yes | The queued message |
| $context | any | no | Delivery attempt-bound variable. It is only passed between pre and post-delivery. |
### Arguments
| Array item | Type | Example | Description |
|------------------------------------------------------|---------|------------------------------|--------------------------------------------------------------------|
| retry | number | `3` | The current retry |
| expires | number | `3600` | The amount of seconds that are left until the message will expire |
| expired | boolean | `false` | If the maximum age or retry count of the message has been exceeded |
| [queue](#predelivery:q9) | array | `["plugin" => ["return"...]` | The queue plugin data (not always available) |
#### Queue
| Array item | Type | Example | Description |
|-------------------------------------------------------|--------|-------------------------------|---------------------------------------------------------|
| [plugin](#predelivery:aq1p) | array | `["return" => "plugin data"]` | Custom queue plugin return value (not always available) |
##### Plugin
| Array item | Type | Example | Description |
|--------------|--------|-------------------------|------------------------------------------|
| return | any | `"plugin return value"` | Any type passed from custom queue plugin |
### Message
| Array item | Type | Example | Description |
|-----------------------------------------------------------------|--------|------------------------------|-----------------------------------------|
| [id](#predelivery:id1) | array | `["transaction" => "18..."` | ID of the message |
| ts | number | `1575558785.1234` | Unix time of transaction |
| serverid | string | `"inbound"` | ID of the server |
| sender | string | `"test@example.org"` | Sender address (envelope), lowercase |
| [senderaddress](#predelivery:a1) | array | `["localpart" => "test"...]` | Sender address (envelope) |
| recipient | string | `"test@example.org"` | Recipient address (envelope), lowercase |
| [recipientaddress](#predelivery:a1) | array | `["localpart" => "test"...]` | Recipient address (envelope) |
| transportid | string | `"inbound"` | ID of the transport profile to be used |
| tenantid | string | `"customidentifier1"` | Tenant ID of the message |
| jobid | string | `"jobidentifier1"` | Job ID of the message |
| priority | number | `1` | The queue priority |
| size | number | `412311` | Message size in bytes |
| [file](#predelivery:f1) | array | `["original" => ...,]` | The message file |
| [dsn](#predelivery:dsn1) | array | `["envid" => ...,]` | The message DSN options |
| context | array | `["data" => ...,]` | The message context data |
#### Id
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-------------------------|
| transaction | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
| queue | number | `1` | Queue ID of the message |
#### Address
| Array item | Type | Example | Description |
|--------------|--------|-----------------|------------------------|
| localpart | string | `"test"` | Local part of address |
| domain | string | `"example.org"` | Domain part of address |
#### File
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| original | FileResource | | An FileResource to be used with the [`File`](#functions:File) class |
| modified | FileResource | | An FileResource to be used with the [`File`](#functions:File) class |
#### DSN
| Array item | Type | Example | Description |
|--------------|--------|---------------------------|-------------------------------------------------------------------|
| envid | string | `"abcdef@example.com"` | The DSN ENVID |
| ret | string | `"HDRS"` | The DSN RET (*HDRS* or *FULL*) |
| notify | array | `"SUCCESS,FAILURE"` | The DSN NOTIFY options (*NEVER*, *SUCCESS*, *FAILURE* or *DELAY*) |
| orcpt | string | `"recipient@example.org"` | The DSN ORCPT recipient address |
## Functions
### Try([options])
Accept the email into the active queue (for delivery as soon as the active queue policies allow). This is the default action.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array. Some are used to override
the properties otherwise chosen based on the email’s metadata and transport settings.
None of the options changed are stored permanent for any consecutive retries.
> * **transportid** (string) Change the transport ID. The default is the current *transportid*.
> * **grouping** (string) Set the grouping. The default is to automatically set it based on the configuration.
> * **server** (string or array) The destination IP-address or hostname as a string, or an associative array with `host` (string) and `mx` (boolean, default `false`). Overrides the transport setting.
> * **port** (number) TCP port to connect to. Overrides the transport setting.
> * **sender** (string or array) Change the sender email address, either as a string or an associative array with a `localpart`, `domain` and `params`. Overrides the queued email’s metadata.
> * **recipient** (string or array) Change the recipient email address, either as a string or an associative array with a `localpart`, `domain` and `params`. Overrides the queued email’s metadata.
> * **helo** (string) The SMTP HELO/EHLO hostname. It can also be specified per source IP. Overrides the transport setting.
> * **sourceip** (array) Source (local) IP(s) to use. The array may contain either strings (of ID’s) or associative arrays with `id`, `address` (literal), `helo` and `proxyprotocol`. The `proxyprotocol` setting uses the same format as `proxyprotocol` in the root `options` array. Overrides the transport setting.
> * **sourceip_random** (boolean) The order in which to choose non suspended sourceips. The default is `true`.
> * **nonlocal_source** (boolean) Allow binding of non-local addresses (BINDANY). The default is `false`.
> * **saslusername** (string) If specified issue a AUTH LOGIN before MAIL FROM. Overrides the transport setting.
> * **saslpassword** (string) If specified issue a AUTH LOGIN before MAIL FROM. Overrides the transport setting.
> * **saslmechanism** (string) If specified issue the SASL mechanism and the saslpassword (used for eg. XOAUTH2).
> * **tls** (string) Use any of the following TLS modes; `disabled`, `optional`, `optional_verify`, `dane`, `dane_fallback_require`, `dane_fallback_require_verify`, `dane_require`, `require`, `require_verify` or `trust`. Overrides the transport setting.
> * **tls_implicit** (boolean) Use implicit TLS (do not use STARTTLS). The default is `false`.
> * **tls_trust_anchor** (array) An array containing *X509Resource* and/or associative arrays of `selector` (The trust selector), `mtype` (The trust match type) and `data` (The trust data (hex encoded)).
> * **tls_sni** (string or boolean) Request a certificate using the SNI extension. If `true` the connected hostname will be used. The default is not to use SNI (`false`).
> * **tls_protocols** (string) Use one or many of the following TLS protocols; `SSLv2`, `SSLv3`, `TLSv1`, `TLSv1.1`, `TLSv1.2` or `TLSv1.3`. Protocols may be separated by `,` and excluded by `!`. The default is `!SSLv2,!SSLv3,!TLSv1,!TLSv1.1`.
> * **tls_ciphers** (string) List of ciphers to support. The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_ciphersuites** (string) List of TLS ciphersuites to support (TLSv1.3). The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_groups** (string) List of TLS groups to offer for TLS key exchange. The default is decided by OpenSSL for each `tls_protocol`.
> * **tls_verify_host** (boolean) Verify certificate hostname (CN). The default is `false`.
> * **tls_verify_name** (array) Hostnames to verify against the certificate’s CN and SAN (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **tls_verify_name_include_host** (boolean) Include the peer you’re connecting to in the verify name list. Unlink `tls_verify_host` which requires it to match regardless of items in the list. The default is `false`.
> * **tls_client_cert** (array or string) If given as an associative array it should include a `x509` (*X509Resource*), `privatekey` (`*PrivateKeyResource`) and optionally a `chain` (*array*) of *X509Resources*. If given as a string use the following id of a certificate in the configuration as client certificate. The default is to not send a client certificate.
> * **xclient** (array) Associative array of XCLIENT attributes to send.
> * **chunking** (boolean) Enable CHUNKING support. Overrides the transport setting.
> * **pipelining** (boolean) Enable PIPELINING support. Overrides the transport setting.
> * **protocol** (string) The protocol to use; `smtp` or `lmtp`. Overrides the transport setting.
> * **mx_include** (array) Filter the MX lookup result, only including ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude** (array) Filter the MX lookup result, removing ones matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **mx_exclude_bypass** (array) Before applying `mx_exclude`, allow the once matching the hostnames/wildcards (NO_PARTIAL_WILDCARDS | SINGLE_LABEL_SUBDOMAINS).
> * **ip_exclude** (array) Filter the IP addresses or networks, removing addresses listed.
> * **ip_exclude_temporary** (boolean) Specify if empty results due to filtered IP or MX addresses should result in a temporary or permanent error. The default is `false` (permanent error).
> * **tenantid** (string) Set tenantid of the message.
> * **jobid** (string) Set jobid of the message.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **during** (number) Set the message duration in seconds relative to the current time. The default is either choosen by the transport or automatically assigned.
> * **timeout** (array) Associative array of [state](#postdelivery:as1) and the timeout in seconds. The default is set according to RFC2821.
> * **connect_timeout** (number) The connect timeout in seconds. The default is `30` seconds.
> * **pooling** (boolean) Enable connection pooling. The default is `false`.
> * **proxyprotocol** (array) Associative array of `server` (IP) and `port` as well as local `sourceip` to use when connecting to the outbound PROXY server.
> * **dsn** (array) Associative array of `envid` (string), `ret` (string), `notify` (string), `orcpt` (string) and `success` (string) which may be set to `"delivered"` to send delivered notification instead of `"relayed"` (default) whenever applicable.
> * **additional_headers** (array) Add additional headers to be sent (in the format of `header-name: header-value`, no encoding or folding is done). This can be useful to add per retry headers.
> * **plugin** (array) Associative array of `id` (string), `arguments` (any type) to enable custom delivery plugins.
### Queue([options])
Queue the message to be retried later.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **hold** (boolean) Put the message in the hold (inactive) queue. The default is `false`.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **delay** (number) the delay in seconds. The default is according to the current transports retry delay.
> * **reason** (string) Optional message to be logged with the message.
> * **increment_retry** (boolean) If the retry count should be increased. The default is `true`.
> * **reset_retry** (boolean) If the retry count should be reset to zero. The default is `false`.
> * **transportid** (string) Set the transport ID. The default is the current *transportid*.
> * **quotas** (array) An array of quotas to be associated with the message, for use with [`queue_quota()`](#functions:queue_quota).
> * **tenantid** (string) Set tenantid of the message.
> * **jobid** (string) Set jobid of the message.
> **Note**
>
> It’s possible to forcefully queue the message exceeding its max retry.
### Bounce([options])
Skip the delivery attempt and execute the post-delivery context with `$arguments["action"]` set to `BOUNCE`. By default this will delete the message from the queue, and generate a DSN (bounce) to the sender.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **dsn** (array) The DSN settings.
The following options are available in the `dsn` array.
> * **transportid** (string) Set the transport ID. The default is either choosen by the transport or automatically assigned.
> * **recipient** (string or array) Set the recipient of the DSN, either as a string or an associative array with a `localpart` and `domain`.
> * **additional_recipients** (array of recipients) A list of additional recipients (BCC).
> * **metadata** (array) Add additional metadata (KVP) to the DSN.
> * **context** (array) Add context data (KVP) to the DSN message.
> * **from** (string or array) Set the From-header address of the DSN, either as a string or an associative array with a `localpart` and `domain`.
> * **from_name** (string) Set the From-header display name of the DSN.
> * **dkim** (array) Set the DKIM options of the DSN (`selector`, `domain`, `key` including the options available in [`MIME.signDKIM()`](#functions:MIME.signDKIM)).
> * **tenantid** (string) Set tenantid of the message.
> * **jobid** (string) Set jobid of the message.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **subject** (string) A custom subject.
> * **subject_prepend** (string) A custom string to append to the subject line (in addition to the original subject).
> * **headers** (array) Array of additional headers to append to the DSN.
> * **readable_mimepart** (string) A full MIME part to be included as the human readable part of the DSN.
> * **ret** (string) Include either `FULL` (message), `HDRS` (headers only) or *false* (do not include the original message) in bounces. The default is `HDRS`.
> * **modified_original** (boolean) Included original message headers as modified by end-of-DATA script. The default is `false` (as received).
> * **fields** (array) Override the original `reportingmta` (string), `receivedfrommta` (string), `remotemta` (string), `status` (array of three numbers), `diagnosticcode` (string), `diagnostictype` (string) and `finalrecipient` (address) fields.
> * **additional_fields** (array) Array of additional fields to append to the DSN.
> * **additional_recipient_fields** (array) Array of additional fields to append to the per recipient fields in the DSN.
> * **exclude_headers** (array) Array of headers that should be removed from the original message.
```hsl
if ($message["recipientaddress"]["localpart"] == "no-reply")
{
Bounce(["dsn" => [
"from_name" => "The Postmaster" // change postmaster address
]]);
}
```
> **Note**
>
> If the message has a null return path (empty sender) or the transport has DSN disabled, it will run the post-delivery with `DELETE` instead.
### Delete([options])
Skip the delivery attempt and execute the post-delivery context with `$arguments["action"]` set to `DELETE`, and optional populate `$arguments["dsn"]["diagnosticcode"]` with a reason. By default this will delete the message from the queue, without generating a DSN (bounce) to the sender.
* **Parameters:**
**options** (*array*) – an options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **reason** (string) The reason for deletion. The default is a system generated message.
### SetMetaData(metadata)
This function updates the queued message’s metadata the hqf file. It is consequentially remembered for the next retry.
The metadata must be an array with both string keys and values.
* **Parameters:**
**metadata** (*array*) – metadata to set
* **Return type:**
none
> **Note**
>
> To work-around the data type limitation of the metadata; data can be encoded using [`json_encode()`](#functions:json_encode).
### GetMetaData()
Get the metadata set by [`SetMetaData()`](#predelivery:predelivery.SetMetaData). If no data was set, an empty array is returned.
* **Returns:**
the data set by [`SetMetaData()`](#predelivery:predelivery.SetMetaData)
* **Return type:**
array
## On script error
On script error `Queue(["delay" => 300, "increment_retry" => false])` is called.
## On implicit termination
If not explicitly terminated then [`Try()`](#predelivery:predelivery.Try) is called.
# Post-delivery
The post-delivery script is executed after a delivery attempt or when a message is deleted or bounced from the queue. If the message was deleted from the queue (either manually or by retention) the `$context` variable will not be defined.
## Variables
These are the pre-defined variables available.
| Variable | Type | Read-only | Description |
|-----------------------------------------------------------|--------|-------------|---------------------------------------------------------------------|
| [$arguments](#postdelivery:v-z1) | array | yes | Context/hook arguments |
| [$message](#postdelivery:v-m1) | array | yes | The queued message (with modifications from pre-delivery) |
| $context | any | yes | This variable is only defined if the pre-delivery has been executed |
### Arguments
| Array item | Type | Example | Description |
|--------------------------------------------------------|---------|-------------------------------------|-------------------------------------------------------------------------------------------------------------|
| retry | number | `3` | The current retry |
| expires | number | `3600` | The amount of seconds that are left until the message will expire |
| expired | boolean | `false` | If the maximum age or retry count of the message has been exceeded |
| action | string | `"DELETE"` | The default action of this execution (`"DELETE"`, `"BOUNCE"`, `"QUEUE"`). Missing on successful deliveries. |
| grouping | string | `"&exampledomains"` | The matched grouping |
| [policy](#postdelivery:p2) | array | `["tags" => ["backoff"], "polic...` | The pickup policy information (if an attempt was made) |
| [attempt](#postdelivery:r1) | array | `["result" => [ "code" => 200, ...` | The delivery attempt result (if an attempt was made) |
| [dsn](#postdelivery:d1) | array | `["action"=> "delayed", ...]` | The DSN action and a DSN message id (if generated). |
| [queue](#postdelivery:i9) | array | `["plugin" => ["return"...]` | The queue plugin data (not always available) |
#### Policy
| Array item | Type | Example | Description |
|---------------------------------------------------------|--------|-------------------------------------|-------------------------|
| tags | array | `["backoff", ...]` | The policy tags applied |
| [policies](#postdelivery:policies1) | array | `[["fields" => ...], ["fields" ...` | The polices applied |
##### Policies
| Array item | Type | Example | Description |
|---------------|---------|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| fields | array | `["remoteip", "recipient...` | List of matched fields; `transportid`, `localip`, `remoteip`, `remotemx`, `recipientdomain`, `tenantid`, `jobid` and `grouping` |
| grouping | array | `["remotemx" => "#google"]` | List of policy groupings, index on `transportid`, `localip`, `remoteip`, `remotemx`, `recipientdomain`, `tenantid`, `jobid` and `grouping` (not always available) |
| dynamic | array | `[0=>["id"=>"...", "tag"..]` | List of all dynamic policies applied (not always available) |
| tags | array | `["backoff", ...]` | The policy tags (not always available) |
| concurrency | number | `100` | The policy concurrency (not always available) |
| rate | array | `[5, 100]` | The policy rate (not always available) |
| ratealgorithm | string | `"fixedwindow"` | The rate algorithm, can be either `fixedwindow` or `tokenbucket` (not always available) |
| stop | boolean | `false` | If the policy evaluation was stopped due to the stop flag |
| properties | array | `["myproperty1" => "foo"]` | The policy properties (not always available) |
#### Attempt
| Array item | Type | Example | Description |
|-----------------------------------------------------------|--------|-------------------------------------------|------------------------------------------------------------|
| [result](#postdelivery:ar1) | array | `["code" => 250, "enhanced" => [1, ...]` | A SMTP protocol response (if available) |
| [error](#postdelivery:ae1) | array | `["temporary" => true, "message" => ...]` | A generic eror message (if no SMTP result) |
| [connection](#postdelivery:ac1) | array | `["localip" => "1.2.3.4", "remoteip"...]` | Connection information |
| duration | number | `1.012` | The delivery attempt duration |
| [plugin](#postdelivery:ac1p) | array | `["return" => "plugin data"]` | Custom delivery plugin return value (not always available) |
##### Result
| Array item | Type | Example | Description |
|------------------------------------------------------|--------|------------------------------|-------------------------------------------------------------------|
| code | number | `250` | A SMTP status code |
| enhanced | array | `[2, 0, 0]` | A SMTP enhanced status code |
| reason | array | `["Ok: queued as 18c19..."]` | A SMTP response text |
| [state](#postdelivery:as1) | string | `"MAIL"` | An enum to indicate which issued SMTP command triggerd the result |
##### Error
| Array item | Type | Example | Description |
|------------------------------------------------------|---------|---------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| temporary | boolean | `true` | If the error may be transient |
| message | string | `"A generic error"` | An error message |
| type | string | `"dns"` | One of `config`, `dns`, `network`, `dane`, `protocol`, `tls` or `plugin` |
| dns | string | `"nullmx"` | One of `other`, `nxdomain`, `nullmx`, `ipfamily`, `nodata` or `excluded` (only available if type is `dns`) |
| [state](#postdelivery:as1) | string | `"MAIL"` | An enum to indicate which issued SMTP command triggerd the result (only available if type is `network` (if connected) and `protocol`) |
##### Connection
| Array item | Type | Example | Description |
|----------------------------------------------------|--------|------------------------------|---------------------------------------------------------------|
| id | string | `"c7a7890f-a080-11ed-9f..."` | The connection id |
| transactions | number | `1` | The number of transactions on this connection |
| localip | string | `"1.2.3.4"` | The source IP used |
| helo | string | `"send.example.com"` | The HELO hostname sent |
| localipid | string | `"bulkip1"` | The source IP ID (name) used (not always available) |
| remoteip | string | `"4.3.2.1"` | The remote IP connected to |
| remotemx | string | `"mail.example.com"` | The remote MX used |
| remotegreet | array | `["mail.example.com ESMTP"]` | The remote greet message (only available for `220` responses) |
| [tls](#postdelivery:atls1) | array | `["started" => true, ...]` | TLS information (if TLS was started) |
###### TLS
| Array item | Type | Example | Description |
|---------------------------------------------------------|---------|-----------------------------|--------------------------------------|
| started | boolean | `true` | If STARTTLS was successfully started |
| protocol | string | `"TLSv1.3"` | The protocol (if available) |
| cipher | string | `"ECDHE-RSA-AES256-SHA384"` | The cipher (if available) |
| keysize | number | `256` | The keysize (if available) |
| [peercert](#postdelivery:p1) | array | | The peer certificate (if available) |
| tlsrpt | string | `"starttls-not-supported"` | The tlsrpt error (if available) |
| [tlsa](#postdelivery:tlsa1) | array | | The tlsa record (if available) |
####### Peercert
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| x509 | X509Resource | | An X509Resource to be used with the [`X509`](#functions:X509) class |
| chain | array | | An array of X509Resource (the chain) |
| error | number | `18` | The peer certificate validation error (see OpenSSLs SSL_get_verify_result(3)) |
####### TLSA
| Array item | Type | Example | Description |
|--------------------------------------------------------|--------|-------------------------|-------------------------------------------|
| error | array | `["temporary" => true]` | The TLSA name resolution error (if error) |
| [records](#postdelivery:tlsa2) | array | `[0=>["usage"=>3` | The TLSA records (if successful) |
######## Records
| Array item | Type | Example | Description |
|--------------|--------|------------------------------|--------------------------|
| usage | number | `3` | The usage |
| selector | number | `1` | The selector |
| mtype | number | `1` | The mtype |
| data | string | `fffffffffffffffffffffff...` | The data (in hex format) |
##### Plugin
| Array item | Type | Example | Description |
|--------------|--------|-------------------------|---------------------------------------------|
| return | any | `"plugin return value"` | Any type passed from custom delivery plugin |
#### DSN
| Array item | Type | Example | Description |
|---------------------------------------------------|--------|------------------------------|--------------------------------------------------------------------------|
| action | string | `"delayed"` | DSN type to be generated (`delayed`, `failed`, `relayed` or `delivered`) |
| [id](#postdelivery:id2) | array | `["transaction" => "18..."` | ID of the DSN message to be generated |
| status | array | `[5, 0, 0]` | The DSN status (if available) |
| diagnostictype | string | `"smtp"` | The DSN diagnostic type (eg. `smtp` or `x-error`) |
| diagnosticcode | string | `"500 message not accepted"` | The DSN diagnostic code |
#### Queue
| Array item | Type | Example | Description |
|-------------------------------------------------------|--------|-------------------------------|---------------------------------------------------------|
| [plugin](#postdelivery:ai1p) | array | `["return" => "plugin data"]` | Custom queue plugin return value (not always available) |
##### Plugin
| Array item | Type | Example | Description |
|--------------|--------|-------------------------|---------------------------------------------|
| return | any | `"plugin return value"` | Any type passed from custom delivery plugin |
### Message
| Array item | Type | Example | Description |
|-----------------------------------------------------------------|--------|------------------------------|-----------------------------------------|
| [id](#postdelivery:id2) | array | `["transaction" => "18..."` | ID of the message |
| ts | number | `1575558785.1234` | Unix time of transaction |
| serverid | string | `"inbound"` | ID of the server |
| sender | string | `"test@example.org"` | Sender address (envelope), lowercase |
| [senderaddress](#postdelivery:z1) | array | `["localpart" => "test"...]` | Sender address (envelope) |
| recipient | string | `"test@example.org"` | Recipient address (envelope), lowercase |
| [recipientaddress](#postdelivery:z1) | array | `["localpart" => "test"...]` | Recipient address (envelope) |
| transportid | string | `"inbound"` | ID of the transport profile to be used |
| tenantid | string | `"customidentifier1"` | Tenant ID of the message |
| jobid | string | `"jobidentifier1"` | Job ID of the message |
| priority | number | `1` | The queue priority |
| size | number | `412311` | Message size in bytes |
| [file](#postdelivery:f2) | array | `["original" => ...,]` | The message file |
| [dsn](#postdelivery:dsn2) | array | `["envid" => ...,]` | The message DSN options |
| context | array | `["data" => ...,]` | The message context data |
#### Id
| Array item | Type | Example | Description |
|--------------|--------|-----------------------------|-------------------------|
| transaction | string | `"18c190a3-93f-47d7-bd..."` | ID of the transaction |
| queue | number | `1` | Queue ID of the message |
#### Address
| Array item | Type | Example | Description |
|--------------|--------|-----------------|------------------------|
| localpart | string | `"test"` | Local part of address |
| domain | string | `"example.org"` | Domain part of address |
#### File
| Array item | Type | Example | Description |
|--------------|--------------|-----------|------------------------------------------------------------------------------------------------------|
| original | FileResource | | An FileResource to be used with the [`File`](#functions:File) class |
| modified | FileResource | | An FileResource to be used with the [`File`](#functions:File) class |
#### DSN
| Array item | Type | Example | Description |
|--------------|--------|---------------------------|-------------------------------------------------------------------|
| envid | string | `"abcdef@example.com"` | The DSN ENVID |
| ret | string | `"HDRS"` | The DSN RET (*HDRS* or *FULL*) |
| notify | array | `"SUCCESS,FAILURE"` | The DSN NOTIFY options (*NEVER*, *SUCCESS*, *FAILURE* or *DELAY*) |
| orcpt | string | `"recipient@example.org"` | The DSN ORCPT recipient address |
## Functions
### Queue([options])
Queue the message to be retried later. This is the default action for non-permanent (temporary) errors while the retry count isn’t exceeded.
Permanent errors include 5xx SMTP responses, permanent DNS failures, etc.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **hold** (boolean) Put the message in the hold (inactive) queue. The default is `false`.
> * **priority** (number) Change the queue priority (0 - normal, 1 - high). The default is the current *priority*.
> * **delay** (number) The delay in seconds. The default is according to the current transports retry delay.
> * **reason** (string) Optional message to be logged with the message.
> * **increment_retry** (boolean) If the retry count should be increased. The default is `true`.
> * **reset_retry** (boolean) If the retry count should be reset to zero. The default is `false`.
> * **transportid** (string) Change the transport ID. The default is the current *transportid*.
> * **quotas** (array) An array of quotas to be associated with the message, for use with [`queue_quota()`](#functions:queue_quota).
> * **tenantid** (string) Set tenantid of the message.
> * **jobid** (string) Set jobid of the message.
> * **dsn** (array) The DSN settings. See [`Bounce()`](#postdelivery:postdelivery.Bounce) for options.
> * **dsn_delayed** (boolean) Override the current transports delay notification setting.
> **Warning**
>
> If the message was delivered (`!isset($arguments["action"])`) this function will raise a runtime error.
> **Note**
>
> It’s possible to forcefully queue the message exceeding its max retry.
### Bounce([options])
Delete the message from the queue, and generating a DSN (bounce) to the sender.
* **Parameters:**
**options** (*array*) – options array
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **dsn** (array) The DSN settings.
The following options are available in the `dsn` array.
> * **transportid** (string) Set the transport ID. The default is either choosen by the transport or automatically assigned.
> * **recipient** (string or array) Set the recipient of the DSN, either as a string or an associative array with a `localpart` and `domain`.
> * **additional_recipients** (array of recipients) A list of additional recipients (BCC).
> * **metadata** (array) Add additional metadata (KVP) to the DSN.
> * **context** (array) Add context data (KVP) to the DSN message.
> * **from** (string or array) Set the From-header address of the DSN, either as a string or an associative array with a `localpart` and `domain`.
> * **from_name** (string) Set the From-header display name of the DSN.
> * **dkim** (array) Set the DKIM options of the DSN (`selector`, `domain`, `key` including the options available in [`MIME.signDKIM()`](#functions:MIME.signDKIM)).
> * **tenantid** (string) Set tenantid of the message.
> * **jobid** (string) Set jobid of the message.
> * **priority** (number) The queue priority (0 - normal, 1 - high). The default is `0`.
> * **force_notify_success** (string) Always send a SUCCESS notification (`relayed` or `delivered`).
> * **subject** (string) A custom subject.
> * **subject_prepend** (string) A custom string to append to the subject line (in addition to the original subject).
> * **headers** (array) Array of additional headers to append to the DSN.
> * **readable_mimepart** (string) A full MIME part to be included as the human readable part of the DSN.
> * **ret** (string) Include either `FULL` (message), `HDRS` (headers only) or *false* (do not include the original message) in bounces. The default is `HDRS`.
> * **modified_original** (boolean) Included original message headers as modified by end-of-DATA script. The default is `false` (as received).
> * **fields** (array) Override the original `reportingmta` (string), `receivedfrommta` (string), `remotemta` (string), `status` (array of three numbers), `diagnosticcode` (string), `diagnostictype` (string) and `finalrecipient` (address) fields.
> * **additional_fields** (array) Array of additional fields to append to the DSN.
> * **additional_recipient_fields** (array) Array of additional fields to append to the per recipient fields in the DSN.
> * **exclude_headers** (array) Array of headers that should be removed from the original message.
```hsl
if ($arguments["action"] == "BOUNCE")
{
Bounce(["dsn" => [
"from_name" => "The Postmaster" // change postmaster address
]]);
}
```
> **Warning**
>
> If the message was delivered (`!isset($arguments["action"])`) this function will raise a runtime error.
> **Note**
>
> If the message has a null return path (empty sender) or the transport has DSN disabled, it will cause a [`Delete()`](#postdelivery:postdelivery.Delete) instead.
### Delete()
Delete the message from the queue, without generating a DSN (bounce) to the sender.
* **Returns:**
doesn’t return, script is terminated
> **Warning**
>
> If the message was delivered (`!isset($arguments["action"])`) this function will raise a runtime error.
### Default([options])
Run the default action pending the current `$arguments["action"]`. This is the default action if not calling [`Queue()`](#postdelivery:postdelivery.Queue), [`Bounce()`](#postdelivery:postdelivery.Bounce) or [`Delete()`](#postdelivery:postdelivery.Delete). If the message was delivered no action will be taken and the script will terminate.
* **Returns:**
doesn’t return, script is terminated
The following options are available in the options array.
> * **dsn** (array) The DSN settings. See [`Bounce()`](#postdelivery:postdelivery.Bounce) for options.
### SetMetaData(metadata)
This function updates the queued message’s metadata in the hqf file. It is consequentially remembered for the next retry.
The metadata must be an array with both string keys and values.
* **Parameters:**
**metadata** (*array*) – metadata to set
* **Return type:**
none
> **Note**
>
> To work-around the data type limitation of the metadata; data can be encoded using [`json_encode()`](#functions:json_encode).
### GetMetaData()
Get the metadata set by [`SetMetaData()`](#postdelivery:postdelivery.SetMetaData). If no data was set, an empty array is returned.
* **Returns:**
the data set by [`SetMetaData()`](#postdelivery:postdelivery.SetMetaData)
* **Return type:**
array
## On script error
On script error the default action is taken.
## On implicit termination
If not explicitly terminated then the default action is taken.
## References
### SMTP states
| | |
|-----------------|------------------------------------------------|
| BANNER | The initial SMTP greeting |
| HELO | |
| EHLO | |
| LHLO | |
| STARTTLS | |
| AUTH-CRAM-MD5 | In reply to sending AUTH CRAM-MD5 command |
| AUTH-PLAIN | In reply to sending AUTH PLAIN command |
| AUTH-LOGIN | In reply to sending AUTH LOGIN command |
| AUTH-LOGIN-USER | In reply to sending AUTH LOGIN username |
| AUTH | In reply to last command of AUTH login attempt |
| XCLIENT | In reply to sending a XCLIENT command |
| MAIL | |
| RCPT | |
| DATA | In reply to sending the DATA command |
| BDAT | In reply to sending the BDAT command |
| EOD | In reply sending the End-of-DATA |
| RSET | |
| NOOP | |
| QUIT | |
# Integrations
The following integrations are available in our [Halon Extras](https://github.com/halon-extras/) repository on Github.
[arc](https://github.com/halon-extras/arc)
> Authenticated Received Chain (rfc8617)
---
[arf](https://github.com/halon-extras/arf)
> Parse ARF reports (rfc5965)
---
[argon2](https://github.com/halon-extras/argon2)
> Argon2 hashing implementation
---
[authentication-results](https://github.com/halon-extras/authentication-results)
> Authentication-Results (rfc7001)
---
[avro](https://github.com/halon-extras/avro)
> Avro encoding and decoding functions
---
[backoff](https://github.com/halon-extras/backoff)
> HSL functions to enable/disable backoff mode on sub-queues using dynamic queue policies.
---
[backoff-ng](https://github.com/halon-extras/backoff-ng)
> HSL functions to enable backoff mode on sub-queues using dynamic queue policies based on responses from the bounce-classifier.
---
[batv](https://github.com/halon-extras/batv)
> Bounce Tag Address Validation (draft-levine-mass-batv-02)
---
[bimi](https://github.com/halon-extras/bimi)
> Brand Indicators for Message Identification (draft-brand-indicators-for-message-identification-10)
---
[bounce-classifier](https://github.com/halon-extras/bounce-classifier)
> Halon Bounce Classifier client
---
[bounce-list](https://github.com/halon-extras/bounce-list)
> Check a bounce list file
---
[cassandra](https://github.com/halon-extras/cassandra)
> Cassandra database client
---
[clamav](https://github.com/halon-extras/clamav)
> ClamAV anti-virus client
---
[cloudmark](https://github.com/halon-extras/cloudmark)
> Cloudmark anti-spam and ip reputation clients
---
[couchbase](https://github.com/halon-extras/couchbase)
> Couchbase database client
---
[cyren](https://github.com/halon-extras/cyren)
> Cyren anti-spam and ip reputation clients
---
[des](https://github.com/halon-extras/des)
> DES / 3DES encryption and decryption
---
[dkim2](https://github.com/halon-extras/dkim2)
> DKIM2 implementation according to spec-2
---
[dlp](https://github.com/halon-extras/dlp)
> DLP client plugin
---
[dmarc](https://github.com/halon-extras/dmarc)
> Domain-based Message Authentication, Reporting & Conformance (rfc7489 and rfc9989)
---
[dovecot](https://github.com/halon-extras/dovecot)
> Dovecot user authentication
---
[dsn](https://github.com/halon-extras/dsn)
> Parse DSN reports (rfc3464 or rfc6533)
---
[eleven](https://github.com/halon-extras/eleven)
> eXpurgate anti-spam client
---
[emailconsul](https://github.com/halon-extras/emailconsul)
> Send email delivery results to Email Consul
---
[env](https://github.com/halon-extras/env)
> Get an environment variable (getenv)
---
[f-secure](https://github.com/halon-extras/f-secure)
> F-secure anti-virus client
---
[file-deliver](https://github.com/halon-extras/file-deliver)
> Deliver messages to files
---
[file-pickup](https://github.com/halon-extras/file-pickup)
> Adds support for pickup directories
---
[go-grpc-hello-world](https://github.com/halon-extras/go-grpc-hello-world)
> A boilerplate for using gRPC from HSL
---
[grpc-deliver](https://github.com/halon-extras/grpc-deliver)
> A boilerplate for delivering mail over gRPC
---
[handlebars](https://github.com/halon-extras/handlebars)
> Handlebars plugin
---
[history-elastic](https://github.com/halon-extras/history-elastic)
> A plugin for logging to Elasticsearch with predefined fields.
---
[http-background](https://github.com/halon-extras/http-background)
> A asynchronous HTTP client
---
[http-bulk](https://github.com/halon-extras/http-bulk)
> HTTP/POST background bulk sender (with file-backed queue)
---
[http-deliver](https://github.com/halon-extras/http-deliver)
> HTTP email delivery plugin (may be used instead of regular SMTP/LMTP delivery)
---
[ident](https://github.com/halon-extras/ident)
> ident client (rfc1413)
---
[influxdb](https://github.com/halon-extras/influxdb)
> InfluxDB database client
---
[interfaces](https://github.com/halon-extras/interfaces)
> Get information about interfaces and local IP-addresses on the system
---
[ip-list](https://github.com/halon-extras/ip-list)
> Lookup IP address from lists of IPs and networks
---
[jlog](https://github.com/halon-extras/jlog)
> A JLog message queue producer
---
[kafka](https://github.com/halon-extras/kafka)
> Kafka producer plugin
---
[kaspersky](https://github.com/halon-extras/kaspersky)
> Kaspersky anti-virus client
---
[language-detection](https://github.com/halon-extras/language-detection)
> Natural language detection plugin
---
[ldap](https://github.com/halon-extras/ldap)
> LDAP database client
---
[levenshtein](https://github.com/halon-extras/levenshtein)
> Levenshtein distance
---
[logger](https://github.com/halon-extras/logger)
> A custom file logger
---
[mariadb](https://github.com/halon-extras/mariadb)
> MariaDB (MySQL) database client
---
[maxmind-geoip](https://github.com/halon-extras/maxmind-geoip)
> MaxMind GeoIP
---
[memcached](https://github.com/halon-extras/memcached)
> Memcached database client
---
[milter](https://github.com/halon-extras/milter)
> Milter protocol v6 client
---
[minger](https://github.com/halon-extras/minger)
> Alt-N Technologies’ MDaemon minger protocol (draft-hathcock-minger-07)
---
[mongodb](https://github.com/halon-extras/mongodb)
> MongoDB database client
---
[msal](https://github.com/halon-extras/msal)
> Microsoft Authentication Library (MSAL) plugin
---
[msui-client](https://github.com/halon-extras/msui-client)
> MSUI client plugin
---
[mta-sts](https://github.com/halon-extras/mta-sts)
> MTA Strict Transport Security (rfc8461)
---
[pdf](https://github.com/halon-extras/pdf)
> PDF plugin
---
[pgp](https://github.com/halon-extras/pgp)
> PGP plugin
---
[pgp-mime](https://github.com/halon-extras/pgp-mime)
> PGP/MIME encrypt/decrypt a MIME message.
---
[policyd-client](https://github.com/halon-extras/policyd-client)
> Halon client for policyd service
---
[postgresql](https://github.com/halon-extras/postgresql)
> PostgreSQL database client
---
[postmastery](https://github.com/halon-extras/postmastery)
> Send email delivery results to Postmastery
---
[proxytlv](https://github.com/halon-extras/proxytlv)
> A library to parse PROXY Protocol v2 TLV
---
[pubsub](https://github.com/halon-extras/pubsub)
> Google Cloud Pub/Sub publish plugin
---
[queue-relinquish](https://github.com/halon-extras/queue-relinquish)
> Relinquish delivery of mail when picked up from the active queue.
---
[rabbitmq](https://github.com/halon-extras/rabbitmq)
> RabbitMQ publish plugin
---
[rabbitmq-consumer](https://github.com/halon-extras/rabbitmq-consumer)
> A RabbitMQ message consumer
---
[radius](https://github.com/halon-extras/radius)
> Radius client for authentication (rfc2865).
---
[rate](https://github.com/halon-extras/rate)
> Rate limiting client plugin
---
[rate-couchbase](https://github.com/halon-extras/rate-couchbase)
> Rate limiting client (Couchbase)
---
[rate-redis](https://github.com/halon-extras/rate-redis)
> Rate limiting client (Redis)
---
[redis](https://github.com/halon-extras/redis)
> Redis client plugin
---
[rspamd](https://github.com/halon-extras/rspamd)
> Rspamd anti-spam client
---
[rust-example](https://github.com/halon-extras/rust-example)
> A simple plugin example written in Rust
---
[s3](https://github.com/halon-extras/s3)
> A S3 Object Storage client
---
[sink](https://github.com/halon-extras/sink)
> Sink email delivery plugin
---
[smime](https://github.com/halon-extras/smime)
> A S/MIME implementation
---
[smtp](https://github.com/halon-extras/smtp)
> A custom SMTP client
---
[sophos](https://github.com/halon-extras/sophos)
> Sophos anti-virus, DLP and anti-spam clients
---
[spamassassin](https://github.com/halon-extras/spamassassin)
> SpamAssassin anti-spam client (spamc)
---
[sqlite](https://github.com/halon-extras/sqlite)
> SQLite client plugin
---
[srs](https://github.com/halon-extras/srs)
> Sender Rewrite Scheme
---
[static-policydivide](https://github.com/halon-extras/static-policydivide)
> Set the amount of cluster nodes statically.
---
[stdout](https://github.com/halon-extras/stdout)
> Print message to stdout/stderr
---
[suppression-list](https://github.com/halon-extras/suppression-list)
> Check a suppression list file
---
[suspend-start](https://github.com/halon-extras/suspend-start)
> Start the queue suspended
---
[suspend-time](https://github.com/halon-extras/suspend-time)
> Suspend delivery at certain times
---
[syslog](https://github.com/halon-extras/syslog)
> Syslog UDP client (rfc3164)
---
[time-rfc2822](https://github.com/halon-extras/time-rfc2822)
> RFC2822 time plugin
---
[tls-rpt](https://github.com/halon-extras/tls-rpt)
> A TLS reporting implementation (rfc8460)
---
[totp](https://github.com/halon-extras/totp)
> Time-based One-time Password Algorithm (rfc6238)
---
[tracking](https://github.com/halon-extras/tracking)
> Rewrite URL:s and add tracking pixels to track user engagement.
---
[twilio](https://github.com/halon-extras/twilio)
> Twilio messaging service integration (SMS)
---
[ulid](https://github.com/halon-extras/ulid)
> Universally Unique Lexicographically Sortable Identifier
---
[vade](https://github.com/halon-extras/vade)
> Vade Secure anti-spam client
---
[varist](https://github.com/halon-extras/varist)
> Varist Hybrid Detection Engine and Anti Virus scanner
---
[warmup](https://github.com/halon-extras/warmup)
> IP warmup plugin
---
[wforce](https://github.com/halon-extras/wforce)
> wforce (weakforce) client
---
[zip](https://github.com/halon-extras/zip)
> ZIP plugin