Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(849)

Side by Side Diff: impl/memory/mail.go

Issue 1525923002: Implement Mail service. (Closed) Base URL: https://github.com/luci/gae.git@filter_user
Patch Set: tests and words Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « impl/memory/context.go ('k') | impl/memory/mail_static_lists.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package memory
6
7 import (
8 "fmt"
9 net_mail "net/mail"
10 "net/textproto"
11 "path/filepath"
12 "strings"
13 "sync"
14
15 "github.com/luci/gae/service/mail"
16 "github.com/luci/gae/service/user"
17 "golang.org/x/net/context"
18 )
19
20 type mailData struct {
21 sync.Mutex
22 queue []*mail.TestMessage
23 admins []string
24 adminsPlain []string
25 }
26
27 // mailImpl is a contextual pointer to the current mailData.
28 type mailImpl struct {
29 data *mailData
30
31 c context.Context
32 }
33
34 var _ mail.Interface = (*mailImpl)(nil)
35
36 // useMail adds a mail.Interface implementation to context, accessible
37 // by mail.Get(c)
38 func useMail(c context.Context) context.Context {
39 data := &mailData{
40 admins: []string{"admin@example.com"},
41 adminsPlain: []string{"admin@example.com"},
42 }
43
44 return mail.SetFactory(c, func(c context.Context) mail.Interface {
45 return &mailImpl{data, c}
46 })
47 }
48
49 func parseEmails(emails ...string) error {
50 for _, e := range emails {
51 if _, err := net_mail.ParseAddress(e); err != nil {
52 return fmt.Errorf("invalid email (%q): %s", e, err)
53 }
54 }
55 return nil
56 }
57
58 func checkMessage(msg *mail.TestMessage, adminsPlain []string, user string) erro r {
59 sender, err := net_mail.ParseAddress(msg.Sender)
60 if err != nil {
61 return fmt.Errorf("unparsable Sender address: %s: %s", msg.Sende r, err)
62 }
63 senderOK := user != "" && sender.Address == user
64 if !senderOK {
65 for _, a := range adminsPlain {
66 if sender.Address == a {
67 senderOK = true
68 break
69 }
70 }
71 }
72 if !senderOK {
73 return fmt.Errorf("invalid Sender: %s", msg.Sender)
74 }
75
76 if len(msg.To) == 0 && len(msg.Cc) == 0 && len(msg.Bcc) == 0 {
77 return fmt.Errorf("one of To, Cc or Bcc must be non-empty")
78 }
79
80 if err := parseEmails(msg.To...); err != nil {
81 return err
82 }
83 if err := parseEmails(msg.Cc...); err != nil {
84 return err
85 }
86 if err := parseEmails(msg.Bcc...); err != nil {
87 return err
88 }
89
90 if len(msg.Body) == 0 && len(msg.HTMLBody) == 0 {
91 return fmt.Errorf("one of Body or HTMLBody must be non-empty")
92 }
93
94 if len(msg.Attachments) > 0 {
95 msg.MIMETypes = make([]string, len(msg.Attachments))
96 for i := range msg.Attachments {
97 n := msg.Attachments[i].Name
98 ext := strings.TrimLeft(strings.ToLower(filepath.Ext(n)) , ".")
99 if badExtensions.Has(ext) {
100 return fmt.Errorf("illegal attachment extension for %q", n)
101 }
102 mimetype := extensionMapping[ext]
103 if mimetype == "" {
104 mimetype = "application/octet-stream"
105 }
106 msg.MIMETypes[i] = mimetype
107 }
108 }
109
110 fixKeys := map[string]string{}
111 for k := range msg.Headers {
112 canonK := textproto.CanonicalMIMEHeaderKey(k)
113 if !okHeaders.Has(canonK) {
114 return fmt.Errorf("disallowed header: %s", k)
115 }
116 if canonK != k {
117 fixKeys[k] = canonK
118 }
119 }
120 for k, canonK := range fixKeys {
121 vals := msg.Headers[k]
122 delete(msg.Headers, k)
123 msg.Headers[canonK] = vals
124 }
125
126 return nil
127 }
128
129 func (m *mailImpl) sendImpl(msg *mail.Message) error {
130 email := ""
131 userSvc := user.Get(m.c)
132 if u := userSvc.Current(); u != nil {
133 email = u.Email
134 }
135
136 m.data.Lock()
137 adminsPlain := m.data.adminsPlain[:]
138 m.data.Unlock()
139
140 testMsg := &mail.TestMessage{Message: *msg}
141
142 if err := checkMessage(testMsg, adminsPlain, email); err != nil {
143 return err
144 }
145 m.data.Lock()
146 m.data.queue = append(m.data.queue, testMsg)
147 m.data.Unlock()
148 return nil
149 }
150
151 func (m *mailImpl) Send(msg *mail.Message) error {
152 return m.sendImpl(msg.Copy())
153 }
154
155 func (m *mailImpl) SendToAdmins(msg *mail.Message) error {
156 msg = msg.Copy()
157 m.data.Lock()
158 ads := m.data.admins[:]
159 m.data.Unlock()
160
161 msg.To = make([]string, len(ads))
162 copy(msg.To, ads)
163
164 return m.sendImpl(msg)
165 }
166
167 func (m *mailImpl) Testable() mail.Testable {
168 return m
169 }
170
171 func (m *mailImpl) SetAdminEmails(emails ...string) {
172 adminsPlain := make([]string, len(emails))
173 for i, e := range emails {
174 adr, err := net_mail.ParseAddress(e)
175 if err != nil {
176 panic(fmt.Errorf("invalid email (%q): %s", e, err))
177 }
178 adminsPlain[i] = adr.Address
179 }
180
181 m.data.Lock()
182 m.data.admins = emails
183 m.data.adminsPlain = adminsPlain
184 m.data.Unlock()
185 }
186
187 func (m *mailImpl) SentMessages() []*mail.TestMessage {
188 m.data.Lock()
189 msgs := m.data.queue[:]
190 m.data.Unlock()
191
192 ret := make([]*mail.TestMessage, len(msgs))
193 for i, m := range msgs {
194 ret[i] = m.Copy()
195 }
196 return ret
197 }
198
199 func (m *mailImpl) Reset() {
200 m.data.Lock()
201 m.data.queue = nil
202 m.data.Unlock()
203 }
OLDNEW
« no previous file with comments | « impl/memory/context.go ('k') | impl/memory/mail_static_lists.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698