Created
May 2, 2017 13:02
-
-
Save corpix/fa9c53acf7d55f8188850c5b4239fea0 to your computer and use it in GitHub Desktop.
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
package main | |
// XXX: Here will be a bunch of shitcoded solutions :) | |
import ( | |
"crypto/sha1" | |
"encoding/base64" | |
"encoding/hex" | |
"fmt" | |
"io" | |
"os" | |
"strings" | |
"github.com/PuerkitoBio/goquery" | |
"github.com/corpix/clistruct" | |
"github.com/davecgh/go-spew/spew" | |
"github.com/urfave/cli" | |
"gopkg.in/gomail.v2" | |
) | |
type Flags struct { | |
SMTP string `value:"smtp.gmail.com"` | |
SMTPPort int `name:"smtp-port" value:"587"` | |
Mime string `value:"text/html"` | |
Embed []string `usage:"A slice of attribute names with base64 encoded content to embed as inline attachments"` | |
From string | |
To string | |
Password string | |
Subject string | |
Body string | |
Attachment []string | |
Debug bool | |
} | |
var ( | |
flags = &Flags{} | |
) | |
type embedding struct { | |
name string | |
id string | |
body gomail.FileSetting | |
headers gomail.FileSetting | |
} | |
func newEmbeddingFromByte(mime string, data []byte) (*embedding, error) { | |
var ( | |
err error | |
) | |
h := sha1.New() | |
_, err = h.Write(data) | |
if err != nil { | |
return nil, err | |
} | |
id := hex.EncodeToString(h.Sum(nil)) | |
t := strings.Split(mime, "/") | |
name := string(id) + `.` + t[len(t)-1] | |
return &embedding{ | |
name: name, | |
id: id, | |
body: gomail.SetCopyFunc( | |
func(w io.Writer) error { | |
_, err := w.Write(data) | |
return err | |
}, | |
), | |
headers: gomail.SetHeader( | |
map[string][]string{ | |
"Content-Type": {mime + `; name="` + name + `"`}, | |
"Content-ID": {id}, | |
}, | |
), | |
}, nil | |
} | |
func embedFromReader(message *gomail.Message, r io.Reader, attributes []string) ([]byte, error) { | |
d, err := goquery.NewDocumentFromReader(r) | |
if err != nil { | |
return nil, err | |
} | |
d. | |
Find("*"). | |
Each(func(k int, s *goquery.Selection) { | |
var ( | |
val string | |
exist bool | |
mimeEnd int | |
mime string | |
dataTypeEnd int | |
data []byte | |
embed *embedding | |
err error | |
) | |
for _, attribute := range attributes { | |
val, exist = s.Attr(attribute) | |
if !exist { | |
return | |
} | |
if !strings.HasPrefix(val, "data:") { | |
// Supports only `data:` atm. | |
fmt.Printf("Unsupported URI '%s', skipping\n", val) | |
return | |
} | |
val = val[5:] | |
mimeEnd = strings.IndexRune(val, ';') | |
if mimeEnd <= 0 { | |
panic(fmt.Errorf("Can not find mime type in '%s'", val)) | |
} | |
mime = val[:mimeEnd] | |
val = val[mimeEnd+1:] | |
dataTypeEnd = strings.IndexRune(val, ',') | |
if dataTypeEnd <= 0 { | |
panic(fmt.Errorf("Can not find data type in '%s'", val)) | |
} | |
if val[:dataTypeEnd] != "base64" { | |
panic(fmt.Errorf("Invalid data type '%s'", val[:dataTypeEnd])) | |
} | |
val = val[dataTypeEnd+1:] | |
data, err = base64.StdEncoding.DecodeString(val) | |
if err != nil { | |
panic(err) | |
} | |
embed, err = newEmbeddingFromByte(mime, data) | |
if err != nil { | |
panic(err) | |
} | |
message.Embed(embed.name, embed.body, embed.headers) | |
s.SetAttr(attribute, "cid:"+embed.id) | |
} | |
}) | |
html, err := d.Html() | |
if err != nil { | |
return nil, err | |
} | |
return []byte(html), nil | |
} | |
func rootAction(flags *Flags, context *cli.Context) error { | |
var ( | |
err error | |
body []byte | |
) | |
m := gomail.NewMessage() | |
m.SetHeader("From", flags.From) | |
m.SetHeader("To", flags.To) | |
m.SetHeader("Subject", flags.Subject) | |
r, err := os.Open(flags.Body) | |
if err != nil { | |
return err | |
} | |
defer r.Close() | |
body, err = embedFromReader(m, r, flags.Embed) | |
if err != nil { | |
return err | |
} | |
m.SetBody(flags.Mime, string(body)) | |
for _, v := range flags.Attachment { | |
m.Attach(v) | |
} | |
d := gomail.NewDialer( | |
flags.SMTP, | |
flags.SMTPPort, | |
flags.From, | |
flags.Password, | |
) | |
err = d.DialAndSend(m) | |
if err != nil { | |
return err | |
} | |
fmt.Printf("Mail to '%s' has been sent\n", flags.To) | |
return nil | |
} | |
func before(flags *Flags, context *cli.Context) error { | |
if flags.Debug { | |
spew.Dump(flags) | |
} | |
return nil | |
} | |
func wrapWithFlags(fn func(*Flags, *cli.Context) error) func(*cli.Context) error { | |
return func(context *cli.Context) error { | |
flags := &Flags{} | |
err := clistruct.FlagsToStruct(context, flags) | |
if err != nil { | |
return err | |
} | |
return fn(flags, context) | |
} | |
} | |
func main() { | |
cliFlags, err := clistruct.FlagsFromStruct(flags) | |
if err != nil { | |
panic(err) | |
} | |
app := cli.NewApp() | |
app.Flags = cliFlags | |
app.Before = wrapWithFlags(before) | |
app.Action = wrapWithFlags(rootAction) | |
app.RunAndExitOnError() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment