mbox

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2023 License: MIT Imports: 10 Imported by: 0

README

mbox

Go Reference Go Test Result Coverage Status

Supporting four different mbox file formats in pure golang.

Package mbox implements a reader and writer for working with mbox files. It also provides a tool to potentially determine the type of mbox format, although it isn't possible to create tool that can definitively determine this.

The package supports four types of mbox files:

  • mboxo
  • mboxrd
  • mboxcl
  • mboxcl2

Use mboxo for the original mbox format.

Use mboxrd to handle lines starting with 'From ' in a way to avoid conflicts by prepending such lines with '>', removing those characters when reading the mail.

Use mboxcl to address lines starting with 'From ' by doing what mboxrd does, but also adding a 'Content-Length' header to the mail that provides the size of the mail's body.

Use mboxcl2 to address the lines starting with 'From ' by doing what mboxcl does, except it doesn't add '>' characters at all.

You may need to know which type to use when reading or writing an mbox, for best results. However, you can try using DetectType() to work out the type of mbox. Thanks go to BenjamenMeyer's Thunderbird Mailbox Deduper code for incentivizing me to create DetectType(), even if I took a different approach.

NOTE: These routines do not concern themselves with file locking. You may want to consider that while working with mbox files on systems that might actively write to the file, such the mbox for a Linux account on a local system. This library simply use the golang writer/reader interfaces.

Hopefully, one may use a new structure to work with file locking once golang exposes a standardized, tested file locking API. Currently, one must work with golang's internal API, or write their own code, for proper file locking.

Installation

go get github.com/tvanriper/mbox

Documentation

Overview

Package mbox provides a flexible mbox reader and writer for four file types.

Index

Examples

Constants

View Source
const (
	MBOXO   int = iota // Specifies the mboxo mail box file type.
	MBOXRD             // Specifies the mboxrd mail box file type.
	MBOXCL             // Specifies the mboxcl mail box file type.
	MBOXCL2            // Specifies the mboxcl2 mail box file type.
)

Variables

View Source
var TimeFormat string = "Mon Jan 02 15:04:05 2006"

TimeFormat describes the way mbox files format the 'From ' date/time field. One may use this with time.Format and time.Parse functions.

Functions

func BuildFrom

func BuildFrom(addr string, date time.Time, moreinfo string) (result string)

BuildFrom creates a from string based on the provided data. A mailer might build this to add to an mbox that it creates.

func DetectType added in v0.1.3

func DetectType(reader io.ReadSeeker) (mboxType int, err error)

DetectType attempts to figure out the type of mbox the reader holds. This is a best-effort attempt to determine the type of mbox file format based on what it sees within the text. When it returns, it attempts to move reader to the beginning of the stream on exit.

It tries to work out the type of file by:

  • Looking for 'Content-Length' in a message's header
  • Looking for '>From ' or '>>From ' (or any number of > character in front of "From "') in the message's body.
  • Using the length of the 'Content-Length', if present, to determine when the body of the message is complete.

With this information, it can guess if the mbox matches one of the file types supported by this library with some degree of certainty.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/tvanriper/mbox"
)

// Imagine this is a file on your filesystem instead of a variable in your code.
const mboxrd2 string = `From bubbles@bubbletown.com Mon Jul 04 14:23:45 2022
From: bubbles@bubbletown.com
To: mrmxpdstk@lazytown.com
Subject: To interpretation

>From all of us, to all of you, be happy!
From mrspam@corporate.corp.com Mon Jul 04 15:02:15 2022
From: mrspam@corporate.corp.com
To: mrmxpdstk@lazytown.com
Subject: Bestest offer in the universe!!11!!

You won't believe these prices!
>From 1 cent to 11 cents, we carry the least expensive
line of jets this side of the Gobi Desert!
`

func main() {
	// Imagine you used os.Open instead of bytes.NewBuffer here.
	reader := bytes.NewReader([]byte(mboxrd2))
	mbType, err := mbox.DetectType(reader)
	if err != nil {
		fmt.Printf("failed to detect mbox type: %s\n", err)
		return
	}
	switch mbType {
	case mbox.MBOXO:
		fmt.Println("MBOXO")
	case mbox.MBOXRD:
		fmt.Println("MBOXRD")
	case mbox.MBOXCL:
		fmt.Println("MBOXCL")
	case mbox.MBOXCL2:
		fmt.Println("MBOXCL2")
	default:
		fmt.Println("Unknown")
	}

}
Output:

MBOXRD

func ParseFrom

func ParseFrom(from string) (addr string, date time.Time, moreinfo string, err error)

ParseFrom parses a from string to its component parts. It helpfully translates the date/time to a time.Time. A mailer might use this information in some way, if needed.

Types

type FileFromFS

type FileFromFS struct {
	Base string // The base folder in which to write/read temporary files.
	// contains filtered or unexported fields
}

FileFromFS provides a structure for working with temporary files used while creating MBOXCL and MBOXCL2 files.

func NewFileFromFS

func NewFileFromFS(base string) *FileFromFS

NewFileFromFS creates a new FileFromFS with the provided base folder. If the base is length 0, it will use os.TempDir() to determine the base folder location. This is the default used when calling NewWriter().

func (*FileFromFS) OpenReader

func (f *FileFromFS) OpenReader(from string) (result io.ReadCloser, err error)

OpenReader opens an io.ReadCloser based on the 'from' provided.

func (*FileFromFS) OpenWriter

func (f *FileFromFS) OpenWriter(from string) (result io.WriteCloser, err error)

OpenWriter opens an io.WriteCloser based on the 'from' provided.

func (*FileFromFS) Remove

func (f *FileFromFS) Remove(from string) (err error)

RemoveFile removes the temporary file created for working with the mbox.

type FromFS

type FromFS interface {
	OpenReader(from string) (result io.ReadCloser, err error)  // Opens a ReadCloser for the item specified by the 'from' field.
	OpenWriter(from string) (result io.WriteCloser, err error) // Opens a WriteCloser for the item specified by the 'from' field.
	Remove(from string) (err error)                            // Removes any information associated with the 'from' field.
}

FromFS describes an interface for providing a reader and writer independent of the underlying file system. Replace MboxWriter.FS with your own implementation if needed. MboxWriter uses this interface for working with MBOXCL and MBOXCL2 files.

type MboxReader

type MboxReader struct {
	Type int // Specifies the type of MboxReader, defaulting to MBOXO.
	// contains filtered or unexported fields
}

MboxReader provides a reader for mbox files.

Example
package main

import (
	"bytes"
	"fmt"
	"net/mail"

	"github.com/tvanriper/mbox"
)

// Imagine this is a file on your filesystem instead of a variable in your code.
const mboxrd string = `From bubbles@bubbletown.com Mon Jul 04 14:23:45 2022
From: bubbles@bubbletown.com
To: mrmxpdstk@lazytown.com
Subject: To interpretation

>From all of us, to all of you, be happy!
From mrspam@corporate.corp.com Mon Jul 04 15:02:15 2022
From: mrspam@corporate.corp.com
To: mrmxpdstk@lazytown.com
Subject: Bestest offer in the universe!!11!!

You won't believe these prices!
>From 1 cent to 11 cents, we carry the least expensive
line of jets this side of the Gobi Desert!
`

func main() {
	// Imagine you used os.Open instead of bytes.NewBuffer here.
	file := bytes.NewBuffer([]byte(mboxrd))
	mailReader := mbox.NewReader(file)
	mailReader.Type = mbox.MBOXRD

	var err error
	mailBytes := bytes.NewBuffer([]byte{})
	for err == nil {
		_, err = mailReader.NextMessage(mailBytes)
		msg, e := mail.ReadMessage(bytes.NewBuffer(mailBytes.Bytes()))
		if e != nil {
			panic(e)
		}
		fmt.Println("From:")
		fmt.Println(msg.Header.Get("From"))
		fmt.Println("Subject:")
		fmt.Println(msg.Header.Get("Subject"))
		mailBytes.Reset()
	}

}
Output:

From:
bubbles@bubbletown.com
Subject:
To interpretation
From:
mrspam@corporate.corp.com
Subject:
Bestest offer in the universe!!11!!

func NewReader

func NewReader(read io.Reader) *MboxReader

NewReader creates a new MboxReader. You may wish to set the Type after instantiation if your mbox is anything other than MBOXO.

func (*MboxReader) NextMessage

func (m *MboxReader) NextMessage(write io.Writer) (from string, err error)

NextMessage writes the next message into the writer. This returns the 'From ' string that separates the mbox email, and an err for an error. This returns an io.EOF error when the last message is read.

type MboxWriter

type MboxWriter struct {
	Type int    // Specifies the type of MboxWriter, defaulting to MBOXO.
	FS   FromFS // A filesystem for working with temporary files that handle MBOXCL/MBOXCL2 mboxes. Defaults to a FileFromFS.
	// contains filtered or unexported fields
}

MboxWriter describes a writer for any of the mbox file types. Use NewWriter to instantiate. Set Type to specify the type. Type is set to MBOXO by default.

Example
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"strings"
	"time"

	"github.com/tvanriper/mbox"
)

// PrintWithLineCount displays the mbox in a clean way for godoc.
func PrintWithLineCount(text string) {
	scanner := bufio.NewScanner(bytes.NewBuffer([]byte(text)))
	lineCount := 0
	for scanner.Scan() {
		lineCount += 1
		line := scanner.Text()
		trimmed := strings.TrimSpace(line)
		l := len(trimmed)
		if l == 0 {
			fmt.Printf("%02d:\n", lineCount)
			continue
		}
		fmt.Printf("%02d: %s\n", lineCount, trimmed)
	}
}

func main() {
	// Imagine these two emails came from some larger piece of software that
	// provides email.

	fromTime1, _ := time.Parse(time.RFC3339, "2022-07-04T14:03:04Z")
	from1 := mbox.BuildFrom("bubbles@bubbletown.com", fromTime1, "")
	email1 := `From: bubbles@bubbletown.com
To: mrmxpdstk@lazytown.com
Subject: To interpretation

From all of us, to all of you, be happy!
`
	fromTime2, _ := time.Parse(time.RFC3339, "2022-07-04T13:12:34Z")
	from2 := mbox.BuildFrom("mrspam@corporate.corp.com", fromTime2, "")
	email2 := `From: mrspam@corporate.corp.com
To: mrmxpdstk@lazytown.com
Subject: Bestest offer in the universe!!11!!
From X: Quoi?

You won't believe these prices!
`

	// Imagine this is on a filesystem instead of bytes in a buffer.
	file := bytes.NewBuffer([]byte{})
	mailWriter := mbox.NewWriter(file)

	var err error
	err = mailWriter.WriteMail(from1, bytes.NewBuffer([]byte(email1)))
	if err != nil {
		panic(err)
	}
	err = mailWriter.WriteMail(from2, bytes.NewBuffer([]byte(email2)))
	if err != nil {
		panic(err)
	}

	fmt.Println("File contents:")
	PrintWithLineCount(file.String())

}
Output:

File contents:
01: From bubbles@bubbletown.com Mon Jul 04 14:03:04 2022
02: From: bubbles@bubbletown.com
03: To: mrmxpdstk@lazytown.com
04: Subject: To interpretation
05:
06: >From all of us, to all of you, be happy!
07:
08: From mrspam@corporate.corp.com Mon Jul 04 13:12:34 2022
09: From: mrspam@corporate.corp.com
10: To: mrmxpdstk@lazytown.com
11: Subject: Bestest offer in the universe!!11!!
12: >From X: Quoi?
13:
14: You won't believe these prices!
15:

func NewWriter

func NewWriter(write io.Writer) (result *MboxWriter)

NewWriter instantiates a new mbox file writer. Subsequent calls to MBoxWriter.WriteMail() will write mbox-formatted output to the writer provided to this function.

func (*MboxWriter) WriteMail

func (m *MboxWriter) WriteMail(from string, mail io.Reader) (err error)

WriteMail adds new mail to the mbox-formatted file stream provided to NewWriter. The 'from' argument may come from a call to MboxReader.NextMessage(), or a tool delivering the mail to the box. The 'mail' argument contains the bytes composing the mail (headers and body).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL