Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c6e4984
Showing
6 changed files
with
458 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.out | ||
*.[68] | ||
_obj | ||
_test | ||
_testmain.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
Copyright (c) 2010, Simon Lipp | ||
|
||
Permission to use, copy, modify, and distribute this software for | ||
any purpose with or without fee is hereby granted, provided that | ||
the above copyright notice and this permission notice appear in all | ||
copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | ||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA | ||
OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | ||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
include $(GOROOT)/src/Make.inc | ||
|
||
TARG=maildir | ||
GOFILES=\ | ||
maildir.go\ | ||
|
||
include $(GOROOT)/src/Make.pkg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# PACKAGE | ||
|
||
package maildir | ||
|
||
This package is used for writing mails to a maildir, according to | ||
the specification located at http://www.courier-mta.org/maildir.html | ||
|
||
|
||
# TYPES | ||
|
||
type Maildir struct { | ||
// contains unexported fields | ||
} | ||
Represent a folder in a maildir. The root folder is usually the Inbox. | ||
|
||
`func New(path string, create bool) (m *Maildir, err os.Error)` | ||
|
||
Open a maildir. If create is true and the maildir does not exist, create it. | ||
|
||
`func (m *Maildir) Child(name string, create bool) (*Maildir, os.Error)` | ||
|
||
Get a subfolder of the current folder. If create is true and the folder does not | ||
exist, create it. | ||
|
||
`func (m *Maildir) CreateMail(data io.Reader) (filename string, err os.Error)` | ||
Write a mail to the maildir folder. The data is not encoded or compressed in any way. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
// Copyright 2010 Simon Lipp. | ||
// Distributed under a BSD-like license. See COPYING for more | ||
// details | ||
|
||
// This package is used for writing mails to a maildir, according to | ||
// the specification located at http://www.courier-mta.org/maildir.html | ||
package maildir | ||
|
||
import ( | ||
"encoding/base64" | ||
"bytes" | ||
"strings" | ||
"sync" | ||
"os" | ||
"io" | ||
"fmt" | ||
"time" | ||
"utf16" | ||
paths "path" | ||
) | ||
|
||
var maildirBase64 = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,") | ||
var counter chan uint | ||
var counterInit sync.Once | ||
|
||
// Represent a folder in a maildir. The root folder is usually the Inbox. | ||
type Maildir struct { | ||
// The root path ends with a /, others don't, so we can have | ||
// the child of a maildir just with path + "." + encodedChildName | ||
path string | ||
} | ||
|
||
func newWithRawPath(path string, create bool) (m *Maildir, err os.Error) { | ||
// start counter if needed, preventing race condition | ||
counterInit.Do(func() { | ||
counter = make(chan uint) | ||
go (func() { | ||
for i := uint(0); true; i++ { | ||
counter <- i | ||
} | ||
})() | ||
}) | ||
|
||
// Create if needed | ||
_, err = os.Stat(path) | ||
if err != nil { | ||
if pe, ok := err.(*os.PathError); ok && pe.Error == os.ENOENT && create { | ||
err = os.MkdirAll(path, 0775) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for _, subdir := range []string{"tmp", "cur", "new"} { | ||
err = os.Mkdir(paths.Join(path, subdir), 0775) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
} else { | ||
return nil, err | ||
} | ||
} | ||
|
||
return &Maildir{path}, nil | ||
} | ||
|
||
// Open a maildir. If create is true and the maildir does not exist, create it. | ||
func New(path string, create bool) (m *Maildir, err os.Error) { | ||
// Ensure that path is not empty and ends with a / | ||
if len(path) == 0 { | ||
path = "." + string(paths.DirSeps[0]) | ||
} else if !strings.Contains(paths.DirSeps, string(path[len(path)-1])) { | ||
path += string(paths.DirSeps[0]) | ||
} | ||
return newWithRawPath(path, create) | ||
} | ||
|
||
// Get a subfolder of the current folder. If create is true and the folder does not | ||
// exist, create it. | ||
func (m *Maildir) Child(name string, create bool) (*Maildir, os.Error) { | ||
var i int | ||
encodedPath := bytes.NewBufferString(m.path + ".") | ||
for i = nextInvalidChar(name); i < len(name); i = nextInvalidChar(name) { | ||
encodedPath.WriteString(name[:i]) | ||
j := nextValidChar(name[i:]) | ||
encode(name[i:i+j], encodedPath) | ||
if j < len(name[i:]) { | ||
name = name[i+j:] | ||
} else { | ||
name = "" | ||
} | ||
} | ||
encodedPath.WriteString(name) | ||
return newWithRawPath(encodedPath.String(), create) | ||
} | ||
|
||
// Write a mail to the maildir folder. The data is not encoded or compressed in any way. | ||
func (m *Maildir) CreateMail(data io.Reader) (filename string, err os.Error) { | ||
hostname, err := os.Hostname() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
basename := fmt.Sprintf("%v.M%vP%v_%v.%v", time.Seconds(), time.Nanoseconds()/1000, os.Getpid(), <-counter, hostname) | ||
tmpname := paths.Join(m.path, "tmp", basename) | ||
file, err := os.Open(tmpname, os.O_WRONLY | os.O_CREAT, 0664) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
size, err := io.Copy(file, data) | ||
if err != nil { | ||
os.Remove(tmpname) | ||
return "", err | ||
} | ||
|
||
newname := paths.Join(m.path, "new", fmt.Sprintf("%v,S=%v", basename, size)) | ||
err = os.Rename(tmpname, newname) | ||
if err != nil { | ||
os.Remove(tmpname) | ||
return "", err | ||
} | ||
|
||
return newname, nil | ||
} | ||
|
||
// Valid (valid = has not to be escaped) chars = | ||
// ASCII 32-127 + "&" + "/" + "." | ||
// We disallow 32 (space) for obvious reasons (avoid to create folders with spaces into their names) | ||
// We disallow 127 because the spec is ambiguous here: it allows 127 but not control characters, | ||
// and 127 is a control character. | ||
func isValidChar(b byte) bool { | ||
if b <= 0x20 || b >= 127 { | ||
return false | ||
} | ||
if b == byte('.') || b == byte('/') || b == byte('&') { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
func nextInvalidChar(s string) int { | ||
for i := 0; i < len(s); i++ { | ||
if !isValidChar(s[i]) { | ||
return i | ||
} | ||
} | ||
return len(s) | ||
} | ||
|
||
func nextValidChar(s string) int { | ||
for i := 0; i < len(s); i++ { | ||
if isValidChar(s[i]) { | ||
return i | ||
} | ||
} | ||
return len(s) | ||
} | ||
|
||
// s is a string of invalid chars, without any "&" | ||
// An encoded sequence is composed of (Python-like pseudo-code): | ||
// "&" + base64(rawSequence.encode('utf-16-be')).strip('=') + "-" | ||
func encodeSequence(s string, buf *bytes.Buffer) { | ||
utf16data := utf16.Encode([]int(s)) | ||
utf16be := make([]byte, len(utf16data)*2) | ||
for i := 0; i < len(utf16data); i++ { | ||
utf16be[i*2] = byte(utf16data[i] >> 8) | ||
utf16be[i*2+1] = byte(utf16data[i] & 0xff) | ||
} | ||
base64data := make([]byte, maildirBase64.EncodedLen(len(utf16be))+2) | ||
maildirBase64.Encode(base64data[1:], utf16be) | ||
endPos := bytes.IndexByte(base64data, byte('=')) | ||
if endPos == -1 { | ||
endPos = len(base64data) | ||
} else { | ||
endPos++ | ||
} | ||
base64data = base64data[:endPos] | ||
base64data[0] = byte('&') | ||
base64data[len(base64data)-1] = byte('-') | ||
buf.Write(base64data) | ||
} | ||
|
||
// s in a string of invalid chars | ||
// "&" is not encoded in a sequence, and must be encoded as "&-", | ||
// so split s as sequences of [^&]* separated by "&" characters | ||
func encode(s string, buf *bytes.Buffer) { | ||
if s[0] == byte('&') { | ||
buf.WriteString("&-") | ||
if len(s) > 1 { | ||
encode(s[1:], buf) | ||
} | ||
} else { | ||
i := strings.Index(s, "&") | ||
if i != -1 { | ||
encodeSequence(s[:i], buf) | ||
encode(s[i:], buf) | ||
} else { | ||
encodeSequence(s, buf) | ||
} | ||
} | ||
} |
Oops, something went wrong.