2014-06-24 21:21:06 +00:00
package data
2014-04-19 22:37:11 +00:00
import (
2014-06-24 21:21:06 +00:00
"labix.org/v2/mgo/bson"
2014-04-19 22:37:11 +00:00
"log"
2014-06-24 21:21:06 +00:00
"regexp"
2014-04-19 22:37:11 +00:00
"strings"
"time"
)
2014-04-20 15:05:50 +00:00
type Messages [ ] Message
2014-04-19 22:37:11 +00:00
type Message struct {
2014-06-24 21:21:06 +00:00
Id string
From * Path
To [ ] * Path
2014-04-19 22:37:11 +00:00
Content * Content
Created time . Time
2014-06-24 21:21:06 +00:00
MIME * MIMEBody // FIXME refactor to use Content.MIME
2014-04-19 22:37:11 +00:00
}
type Path struct {
2014-06-24 21:21:06 +00:00
Relays [ ] string
2014-04-19 22:37:11 +00:00
Mailbox string
2014-06-24 21:21:06 +00:00
Domain string
Params string
2014-04-19 22:37:11 +00:00
}
type Content struct {
Headers map [ string ] [ ] string
2014-06-24 21:21:06 +00:00
Body string
Size int
MIME * MIMEBody
2014-04-19 22:37:11 +00:00
}
type SMTPMessage struct {
From string
2014-06-24 21:21:06 +00:00
To [ ] string
2014-04-19 22:37:11 +00:00
Data string
Helo string
}
2014-04-21 11:14:47 +00:00
type MIMEBody struct {
2014-04-21 21:32:34 +00:00
Parts [ ] * Content
2014-04-21 11:14:47 +00:00
}
2014-04-27 23:21:57 +00:00
// TODO support nested MIME content
2014-04-23 23:22:50 +00:00
func ParseSMTPMessage ( m * SMTPMessage , hostname string ) * Message {
2014-04-19 22:37:11 +00:00
arr := make ( [ ] * Path , 0 )
for _ , path := range m . To {
arr = append ( arr , PathFromString ( path ) )
}
msg := & Message {
2014-06-24 21:21:06 +00:00
Id : bson . NewObjectId ( ) . Hex ( ) ,
From : PathFromString ( m . From ) ,
To : arr ,
2014-04-19 22:37:11 +00:00
Content : ContentFromString ( m . Data ) ,
Created : time . Now ( ) ,
}
2014-04-21 21:32:34 +00:00
if msg . Content . IsMIME ( ) {
log . Printf ( "Parsing MIME body" )
msg . MIME = msg . Content . ParseMIMEBody ( )
}
2014-06-24 21:21:06 +00:00
2014-04-23 23:22:50 +00:00
msg . Content . Headers [ "Message-ID" ] = [ ] string { msg . Id + "@" + hostname }
msg . Content . Headers [ "Received" ] = [ ] string { "from " + m . Helo + " by " + hostname + " (Go-MailHog)\r\n id " + msg . Id + "@" + hostname + "; " + time . Now ( ) . Format ( time . RFC1123Z ) }
2014-04-19 22:37:11 +00:00
msg . Content . Headers [ "Return-Path" ] = [ ] string { "<" + m . From + ">" }
return msg
}
2014-04-21 11:14:47 +00:00
func ( content * Content ) IsMIME ( ) bool {
2014-04-27 23:21:57 +00:00
header , ok := content . Headers [ "Content-Type" ]
2014-06-24 21:21:06 +00:00
if ! ok {
return false
}
2014-04-27 23:21:57 +00:00
return strings . HasPrefix ( header [ 0 ] , "multipart/" )
2014-04-21 11:14:47 +00:00
}
func ( content * Content ) ParseMIMEBody ( ) * MIMEBody {
re := regexp . MustCompile ( "boundary=\"([^\"]+)\"" )
2014-04-21 21:32:34 +00:00
match := re . FindStringSubmatch ( content . Headers [ "Content-Type" ] [ 0 ] )
2014-04-21 11:14:47 +00:00
log . Printf ( "Got boundary: %s" , match [ 1 ] )
2014-04-21 21:32:34 +00:00
2014-06-24 21:21:06 +00:00
p := strings . Split ( content . Body , "--" + match [ 1 ] )
2014-04-21 21:32:34 +00:00
parts := make ( [ ] * Content , 0 )
for m := range p {
if len ( p [ m ] ) > 0 {
2014-04-27 23:21:57 +00:00
part := ContentFromString ( strings . Trim ( p [ m ] , "\r\n" ) )
2014-06-24 21:21:06 +00:00
if part . IsMIME ( ) {
2014-04-27 23:21:57 +00:00
log . Printf ( "Parsing inner MIME body" )
part . MIME = part . ParseMIMEBody ( )
}
parts = append ( parts , part )
2014-04-21 21:32:34 +00:00
}
}
return & MIMEBody {
Parts : parts ,
}
2014-04-21 11:14:47 +00:00
}
2014-04-19 22:37:11 +00:00
func PathFromString ( path string ) * Path {
relays := make ( [ ] string , 0 )
email := path
2014-06-24 21:21:06 +00:00
if strings . Contains ( path , ":" ) {
2014-04-19 22:37:11 +00:00
x := strings . SplitN ( path , ":" , 2 )
r , e := x [ 0 ] , x [ 1 ]
email = e
relays = strings . Split ( r , "," )
}
mailbox , domain := "" , ""
2014-06-24 21:21:06 +00:00
if strings . Contains ( email , "@" ) {
2014-04-19 22:37:11 +00:00
x := strings . SplitN ( email , "@" , 2 )
mailbox , domain = x [ 0 ] , x [ 1 ]
} else {
mailbox = email
}
return & Path {
2014-06-24 21:21:06 +00:00
Relays : relays ,
2014-04-19 22:37:11 +00:00
Mailbox : mailbox ,
2014-06-24 21:21:06 +00:00
Domain : domain ,
Params : "" , // FIXME?
2014-04-19 22:37:11 +00:00
}
}
func ContentFromString ( data string ) * Content {
2014-04-21 21:32:34 +00:00
log . Printf ( "Parsing Content from string: '%s'" , data )
2014-04-21 11:14:47 +00:00
x := strings . SplitN ( data , "\r\n\r\n" , 2 )
2014-04-19 22:37:11 +00:00
h := make ( map [ string ] [ ] string , 0 )
2014-04-26 11:16:57 +00:00
if len ( x ) == 2 {
headers , body := x [ 0 ] , x [ 1 ]
hdrs := strings . Split ( headers , "\r\n" )
var lastHdr = ""
for _ , hdr := range hdrs {
if lastHdr != "" && strings . HasPrefix ( hdr , " " ) {
h [ lastHdr ] [ len ( h [ lastHdr ] ) - 1 ] = h [ lastHdr ] [ len ( h [ lastHdr ] ) - 1 ] + hdr
} else if strings . Contains ( hdr , ": " ) {
y := strings . SplitN ( hdr , ": " , 2 )
key , value := y [ 0 ] , y [ 1 ]
// TODO multiple header fields
h [ key ] = [ ] string { value }
lastHdr = key
} else {
log . Printf ( "Found invalid header: '%s'" , hdr )
}
}
return & Content {
2014-06-24 21:21:06 +00:00
Size : len ( data ) ,
2014-04-26 11:16:57 +00:00
Headers : h ,
2014-06-24 21:21:06 +00:00
Body : body ,
2014-04-26 11:16:57 +00:00
}
} else {
return & Content {
2014-06-24 21:21:06 +00:00
Size : len ( data ) ,
2014-04-26 11:16:57 +00:00
Headers : h ,
2014-06-24 21:21:06 +00:00
Body : x [ 0 ] ,
2014-04-26 11:16:57 +00:00
}
2014-04-19 22:37:11 +00:00
}
}