Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
You can edit this page using
wiki syntax
for markup.
Article contents:
[[tags: eggs]] [[toc:]] == internet-message === Description {{internet-message}} is a collection of parser combinators for the grammar defined in [[http://www.ietf.org/rfc/rfc5322.txt|RFC 5322]] (Internet Message Format). === Usage The combinator procedures in this library are based on the interface provided by the [[abnf]] library. Each procedure exported by {{internet-message}} parser combinator of the form {{(lambda (cont s) ...)}}, which takes a continuation and input stream. For instance, the {{message}} parser combinator can be used to parse messages: <enscript highlight="scheme"> ;; A procedure which creates an input stream from a string (define (string->input-stream s) `(() ,(string->list s))) ;; A procedure used to report parse errors (define (err s) (print "internet message error on stream: " s) (list)) (let* (;; Parser combinator procedure which takes continuation and input stream (parse-message (lambda (cont s) ((message) (compose cont car) err s))) (my-message "From: John Doe <jdoe@machine.example>\r\nTo: Mary Smith <mary@example.net>\r\nSubject: Saying Hello\r\nDate: Fri, 21 Nov 1997 09:55:06 -0600\r\nMessage-ID: <1234@local.machine.example>\r\n\r\nThis is a message just to say hello.\r\nSo, \r\n\r\n\"Hello\".") ) (parse-message (lambda (s) (test (apply sprintf "~S -> ~S" p) res s)) (string->input-stream inp)) ) -> (message (fields (From (mailbox-list (mailbox (display-name (" John " "Doe ")) (local-part "jdoe") (domain "machine.example")))) (To (mailbox (display-name (" Mary " "Smith ")) (local-part "mary") (domain "example.net"))) (Subject " Saying Hello") (Date (day-of-week "Fri") (date "21" "Nov" "1997") (time "09" "55" "06" "-" "06" "00")) (Message-id (message-id "1234" "local.machine.example"))) (body "This is a message just to say hello." "So, " "\"Hello\".")) </enscript> === Library Procedures The following procedures are exported: <procedure>fields</procedure> This parser will parse an arbitrary number of header fields as defined in the RFC. For each field, an appropriate alist is created. The following fields are recognized: * {{from}} * {{sender}} * {{return-path}} * {{reply-to}} * {{to}} * {{cc}} * {{bcc}} * {{message-id}} * {{in-reply-to}} * {{references}} * {{subject}} * {{comments}} * {{keywords}} * {{orig-date}} * {{resent-date}} * {{resent-from}} * {{resent-sender}} * {{resent-to}} * {{resent-cc}} * {{resent-bcc}} * {{resent-msg-id}} * {{resent-reply-to}} * {{received}} * {{optional-field}} <procedure>body</procedure> This parser will parse a message body as specified by the RFC; that is, any number of text characters, which may be divided into separate lines by {{CRLF}}. <procedure>message</procedure> This parser will parse a complete message as defined by the RFC and it will break it down into the separate header fields and the message body. <procedure>comment</procedure> This parser parses comment text, as defined by the RFC. Comments may nest. === internet-message-lens: bidirectional parsing {{internet-message-lens}} builds on [[abnf]]'s {{abnf-lens}} component to add bidirectional transforms to a representative part of the grammar above: besides parsing text into a value, each rule below can also print a value back out as text that the same rule accepts. A {{From:}} header built as a {{NamedMailbox}} record, for instance, prints as {{"From: John Doe <jdoe@example.com>\r\n"}}, and parsing that line back reproduces the record. Every rule below is a {{bp}} (bidirectional parser), used with {{bp-parse}} and {{bp-print}}; the {{bp}} record and these two procedures, along with {{bi-iso}}, {{bi-seq}}, {{define-bi-rule}}, {{define-bi-datatype}}, and the other combinators the rules below are built from, are documented on the [[abnf]] page. A record type declared with {{define-bi-rule}} gets a constructor, a predicate, and one accessor per field, the same as {{define-record-type}}. A sum type declared with {{define-bi-datatype}} gets a constructor per variant and a predicate. Comments and folding whitespace are canonicalized rather than preserved: dropped when parsing, and printed back as either nothing or a single space. A value built by a program therefore always prints as valid, readable text, and a value obtained by parsing always prints back to text with the same meaning. Out of scope: the trace fields ({{Received}}, {{Return-Path}}), the Unicode text variants, and {{parts}}. ==== Words, phrases, and unstructured text <procedure>(bi-atom) => BP</procedure><br> A single atom (RFC 5322 section 3.2.3), such as {{"jdoe"}}: one or more letters, digits, or the punctuation marks {{!#$%&'*+-/=?^_`{|}~}}, folded to a string. <procedure>(bi-dot-atom) => BP</procedure><br> <procedure>(bi-dot-atom-text-string) => BP</procedure><br> A dot-separated run of atoms, such as {{"jane.doe"}}, folded to one string with the dots kept as part of it. {{bi-dot-atom}} additionally allows surrounding comments and folding whitespace, the way {{dot-atom}} does in the original grammar; {{bi-dot-atom-text-string}} does not, the way {{dot-atom-text}} does not, and is used where the RFC calls for {{dot-atom-text}} directly (a message ID's left and right parts, for instance). <procedure>(bi-quoted-string) => BP</procedure><br> A quoted string, such as {{"\"Joe Q. Public\""}}. The surrounding quotes are dropped from the value and reconstructed on print, so the value itself is just the plain content string, e.g. {{"Joe Q. Public"}}; an embedded quote or backslash is escaped automatically on print. <procedure>(bi-word) => BP</procedure><br> An atom or a quoted string, whichever the input is. On print, a plain Scheme string that is entirely made of atom characters prints unquoted; any other string (containing a space, a quote, ...) prints quoted. <procedure>(bi-phrase) => BP</procedure><br> <procedure>(bi-display-name) => BP</procedure><br> One or more words, folded to a list of word strings, e.g. {{("John" "Doe")}} for {{"John Doe"}}. {{bi-display-name}} is the same rule under the name the RFC uses for it in an address. <procedure>(bi-unstructured) => BP</procedure><br> Free text such as a {{Subject:}} value, folded to a single string; an embedded line fold prints back as one canonical space. ==== Date and time <record>date-spec DAY MONTH YEAR</record><br> <procedure>(bi-date-spec) => BP</procedure><br> A calendar date, e.g. the {{"29"}} {{"Aug"}} {{"2008"}} in {{29 Aug 2008}}. Accessors: {{date-spec-day}}, {{date-spec-month}}, {{date-spec-year}}. <record>time-spec HOUR MINUTE SECOND ZONE-SIGN ZONE-HOUR ZONE-MINUTE</record><br> <procedure>(bi-time-spec) => BP</procedure><br> A time of day and zone offset, e.g. {{12:21:46 +0200}}. {{SECOND}} is {{#f}} when absent, as in {{12:21}}; {{ZONE-SIGN}} is the character {{#\+}} or {{#\-}}. Accessors: {{time-spec-hour}}, {{time-spec-minute}}, {{time-spec-second}}, {{time-spec-zone-sign}}, {{time-spec-zone-hour}}, {{time-spec-zone-minute}}. <record>date-time-spec DAY-OF-WEEK DATE TIME</record><br> <procedure>(bi-date-time-spec) => BP</procedure><br> A complete {{Date:}}-style value, e.g. {{Fri, 29 Aug 2008 12:21:46 +0200}}. {{DAY-OF-WEEK}} is a bare weekday string such as {{"Fri"}}, or {{#f}} when the input has none -- either way, printing supplies exactly one correctly-placed separator. {{DATE}} is a {{date-spec}} and {{TIME}} a {{time-spec}}. Accessors: {{date-time-spec-day-of-week}}, {{date-time-spec-date}}, {{date-time-spec-time}}. ==== Addresses <record>addr-spec LOCAL-PART DOMAIN</record><br> <procedure>(bi-addr-spec) => BP</procedure><br> A bare e-mail address, e.g. {{jdoe@machine.example}}. {{LOCAL-PART}} and {{DOMAIN}} are plain strings; a quoted local part or a bracketed domain literal is unwrapped the same way a quoted-string is elsewhere in this module. Accessors: {{addr-spec-local-part}}, {{addr-spec-domain}}. '''mailbox''' -- a [[datatype]], predicate {{mailbox?}}<br> <procedure>(bi-mailbox) => BP</procedure><br> <procedure>(bi-mailbox-list) => BP</procedure><br> One mailbox: either (NamedMailbox DISPLAY-NAME ADDRESS) ; DISPLAY-NAME as in bi-phrase, ADDRESS an addr-spec (BareMailbox ADDRESS) ; ADDRESS an addr-spec {{NamedMailbox}} prints with a space before the angle brackets, e.g. {{John Doe <jdoe@machine.example>}}; {{BareMailbox}} prints as the bare address alone. {{bi-mailbox-list}} matches one or more, comma-separated, folded to a list of {{mailbox}} values. <record>group DISPLAY-NAME MAILBOXES</record><br> <procedure>(bi-group) => BP</procedure><br> <procedure>(bi-group-list) => BP</procedure><br> A named group of mailboxes, e.g. {{A Group: Ed Jones <c@a.test>, joe@where.test;}}. {{MAILBOXES}} is a (possibly empty) list of {{mailbox}} values. {{bi-group-list}} is the group's own mailbox-list-or-nothing rule, exported in case it is useful on its own. Accessors: {{group-display-name}}, {{group-mailboxes}}. '''address''' -- a [[datatype]], predicate {{address?}}<br> <procedure>(bi-address) => BP</procedure><br> <procedure>(bi-address-list) => BP</procedure><br> <procedure>(bi-address-list-or-empty) => BP</procedure><br> A single mailbox or a group: (MailboxAddress MAILBOX) ; MAILBOX a mailbox (GroupAddress GROUP) ; GROUP a group {{bi-address-list}} matches one or more, comma-separated, folded to a list of {{address}} values; {{bi-address-list-or-empty}} additionally allows an empty list (for {{Bcc:}}-style headers, which may carry no addresses at all). ==== Message identifiers <record>msg-id ID-LEFT ID-RIGHT</record><br> <procedure>(bi-msg-id) => BP</procedure><br> <procedure>(bi-msg-id-list1) => BP</procedure><br> A message identifier, e.g. {{<1234@local.machine.example>}}, with the angle brackets dropped, not stored. {{bi-msg-id-list1}} matches one or more, with no separator character required between them (each one self-delimits), folded to a list of {{msg-id}} values, and printed with a canonical space between them. Accessors: {{msg-id-id-left}}, {{msg-id-id-right}}. ==== Body <procedure>(bi-body) => BP</procedure><br> A message body, folded to a list of line strings, one per line, with the {{CRLF}}s between them dropped, not stored. A run of two or more consecutive {{CRLF}}s -- a blank line in the body -- collapses to a single one on print, the same way {{internet-message.scm}}'s own body grammar collapses it on parse, rather than printing back as an empty line of its own. ==== Header fields and messages '''header-field''' -- a [[datatype]], predicate {{header-field?}}<br> <procedure>(bi-header-field) => BP</procedure><br> <procedure>(bi-fields) => BP</procedure><br> One header field. Every recognized header is its own variant, its fixed keyword printed with a canonical single space before the value that follows it: <table> <tr><th>Variant</th><th>Header</th><th>Field(s)</th></tr> <tr><td>{{FromField}}</td><td>{{From:}}</td><td>{{MAILBOXES}}, a list of {{mailbox}}</td></tr> <tr><td>{{SenderField}}</td><td>{{Sender:}}</td><td>{{MAILBOX}}</td></tr> <tr><td>{{ReplyToField}}</td><td>{{Reply-To:}}</td><td>{{ADDRESSES}}, a list of {{address}}</td></tr> <tr><td>{{ToField}}</td><td>{{To:}}</td><td>{{ADDRESSES}}</td></tr> <tr><td>{{CcField}}</td><td>{{Cc:}}</td><td>{{ADDRESSES}}</td></tr> <tr><td>{{BccField}}</td><td>{{Bcc:}}</td><td>{{ADDRESSES}}, possibly empty</td></tr> <tr><td>{{MessageIdField}}</td><td>{{Message-ID:}}</td><td>{{ID}}, a {{msg-id}}</td></tr> <tr><td>{{InReplyToField}}</td><td>{{In-Reply-To:}}</td><td>{{IDS}}, a list of {{msg-id}}</td></tr> <tr><td>{{ReferencesField}}</td><td>{{References:}}</td><td>{{IDS}}</td></tr> <tr><td>{{SubjectField}}</td><td>{{Subject:}}</td><td>{{TEXT}}, a string</td></tr> <tr><td>{{CommentsField}}</td><td>{{Comments:}}</td><td>{{TEXT}}</td></tr> <tr><td>{{KeywordsField}}</td><td>{{Keywords:}}</td><td>{{PHRASES}}, a list of phrases (each a list of word strings)</td></tr> <tr><td>{{DateField}}</td><td>{{Date:}}</td><td>{{DATE}}, a {{date-time-spec}}</td></tr> <tr><td>{{ResentDateField}}</td><td>{{Resent-Date:}}</td><td>{{DATE}}</td></tr> <tr><td>{{ResentFromField}}</td><td>{{Resent-From:}}</td><td>{{MAILBOXES}}</td></tr> <tr><td>{{ResentSenderField}}</td><td>{{Resent-Sender:}}</td><td>{{MAILBOX}}</td></tr> <tr><td>{{ResentToField}}</td><td>{{Resent-To:}}</td><td>{{ADDRESSES}}</td></tr> <tr><td>{{ResentCcField}}</td><td>{{Resent-Cc:}}</td><td>{{ADDRESSES}}</td></tr> <tr><td>{{ResentBccField}}</td><td>{{Resent-Bcc:}}</td><td>{{ADDRESSES}}, possibly empty</td></tr> <tr><td>{{ResentMessageIdField}}</td><td>{{Resent-Message-ID:}}</td><td>{{ID}}</td></tr> <tr><td>{{ResentReplyToField}}</td><td>{{Resent-Reply-To:}}</td><td>{{ADDRESSES}}</td></tr> <tr><td>{{OptionalField}}</td><td>any other name</td><td>{{NAME}} (the exact header name, case preserved) and {{TEXT}}</td></tr> </table> Unlike {{internet-message.scm}}'s {{fields}} parser, {{OptionalField}} keeps the header name as a plain string in its original casing rather than a titlecased symbol, since a printed value has to reparse to the same value, and a case-preserving string does that with no extra work. {{bi-fields}} matches zero or more header fields in a row, folded to a list of {{header-field}} values, trying the specific headers above before falling back to {{OptionalField}} -- the same order {{internet-message.scm}}'s own {{fields}} parser uses. <record>message FIELDS BODY</record><br> <procedure>(bi-message) => BP</procedure><br> A complete message. {{FIELDS}} is a list of {{header-field}} values. {{BODY}} is {{#f}} when the message has no blank line and body at all, or a (possibly empty) list of line strings when it does -- the two are easy to conflate, but not the same thing. Accessors: {{message-fields}}, {{message-body}}. ==== Example <enscript highlight="scheme"> (import internet-message-lens abnf-lens) (define (parse-error s) (error "could not parse" s)) (define msg (car (car (bp-parse bi-message (string-append "From: John Doe <jdoe@machine.example>\r\n" "To: Mary Smith <mary@example.net>\r\n" "Subject: Saying Hello\r\n" "\r\n" "This is a message just to say hello.") parse-error)))) (bp-print bi-message msg) ;; => "From: John Doe <jdoe@machine.example>\r\nTo: Mary Smith <mary@example.net>\r\nSubject: Saying Hello\r\n\r\nThis is a message just to say hello." ;; Building a message from scratch works the same way: (bp-print bi-message (make-message (list (FromField (list (BareMailbox (make-addr-spec "jdoe" "example.com"))))) #f)) ;; => "From: jdoe@example.com\r\n" </enscript> The test suite, [[https://github.com/iraikov/chicken-internet-message/blob/master/tests/lens-run.scm|tests/lens-run.scm]], has one round-trip test group per rule above. === Repository [[https://github.com/iraikov/chicken-internet-message|https://github.com/iraikov/chicken-internet-message]] === Requires * [[abnf]] === Version History * 8.0 Ported to CHICKEN 6; added internet-message-lens, a bidirectional (lens) counterpart covering a representative part of the grammar * 7.0 Ported to CHICKEN 5 * 5.3 Bug fix in received-token * 5.2 Updated test script to return proper exit code * 5.1 Compatibility with improved CharLex->CoreABNF constructor * 5.0 Compatibility with abnf 5 * 4.1-4.2 Typeclass interface fixes * 4.0 Implemented typeclass interface * 3.1 Additional parsing combinators exported * 3.0 Changes to the interface of the fields parser * 2.0 Extensions to the header function and many bugfixes * 1.3 Update to reflect changes in lexgen * 1.1 Fix in date parsing * 1.0 Initial release === License Based on the Haskell Rfc2822 module by Peter Simons. Copyright 2009-2026 Ivan Raikov. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A full copy of the GPL license can be found at <http://www.gnu.org/licenses/>.
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you multiply 1 by 1?