go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/memory/mail.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package memory 16 17 import ( 18 "context" 19 "fmt" 20 net_mail "net/mail" 21 "net/textproto" 22 "path/filepath" 23 "strings" 24 "sync" 25 26 "go.chromium.org/luci/gae/service/mail" 27 "go.chromium.org/luci/gae/service/user" 28 ) 29 30 type mailData struct { 31 sync.Mutex 32 queue []*mail.TestMessage 33 admins []string 34 adminsPlain []string 35 } 36 37 // mailImpl is a contextual pointer to the current mailData. 38 type mailImpl struct { 39 context.Context 40 41 data *mailData 42 } 43 44 var _ mail.RawInterface = (*mailImpl)(nil) 45 46 // useMail adds a mail.RawInterface implementation to context, accessible 47 // by mail.Raw(c) or the exported mail methods. 48 func useMail(c context.Context) context.Context { 49 data := &mailData{ 50 admins: []string{"admin@example.com"}, 51 adminsPlain: []string{"admin@example.com"}, 52 } 53 54 return mail.SetFactory(c, func(ic context.Context) mail.RawInterface { 55 return &mailImpl{ic, data} 56 }) 57 } 58 59 func parseEmails(emails ...string) error { 60 for _, e := range emails { 61 if _, err := net_mail.ParseAddress(e); err != nil { 62 return fmt.Errorf("invalid email (%q): %s", e, err) 63 } 64 } 65 return nil 66 } 67 68 func checkMessage(msg *mail.TestMessage, adminsPlain []string, user string) error { 69 sender, err := net_mail.ParseAddress(msg.Sender) 70 if err != nil { 71 return fmt.Errorf("unparsable Sender address: %s: %s", msg.Sender, err) 72 } 73 senderOK := user != "" && sender.Address == user 74 if !senderOK { 75 for _, a := range adminsPlain { 76 if sender.Address == a { 77 senderOK = true 78 break 79 } 80 } 81 } 82 if !senderOK { 83 return fmt.Errorf("invalid Sender: %s", msg.Sender) 84 } 85 86 if len(msg.To) == 0 && len(msg.Cc) == 0 && len(msg.Bcc) == 0 { 87 return fmt.Errorf("one of To, Cc or Bcc must be non-empty") 88 } 89 90 if err := parseEmails(msg.To...); err != nil { 91 return err 92 } 93 if err := parseEmails(msg.Cc...); err != nil { 94 return err 95 } 96 if err := parseEmails(msg.Bcc...); err != nil { 97 return err 98 } 99 100 if len(msg.Body) == 0 && len(msg.HTMLBody) == 0 { 101 return fmt.Errorf("one of Body or HTMLBody must be non-empty") 102 } 103 104 if len(msg.Attachments) > 0 { 105 msg.MIMETypes = make([]string, len(msg.Attachments)) 106 for i := range msg.Attachments { 107 n := msg.Attachments[i].Name 108 ext := strings.TrimLeft(strings.ToLower(filepath.Ext(n)), ".") 109 if badExtensions.Has(ext) { 110 return fmt.Errorf("illegal attachment extension for %q", n) 111 } 112 mimetype := extensionMapping[ext] 113 if mimetype == "" { 114 mimetype = "application/octet-stream" 115 } 116 msg.MIMETypes[i] = mimetype 117 } 118 } 119 120 fixKeys := map[string]string{} 121 for k := range msg.Headers { 122 canonK := textproto.CanonicalMIMEHeaderKey(k) 123 if !okHeaders.Has(canonK) { 124 return fmt.Errorf("disallowed header: %s", k) 125 } 126 if canonK != k { 127 fixKeys[k] = canonK 128 } 129 } 130 for k, canonK := range fixKeys { 131 vals := msg.Headers[k] 132 delete(msg.Headers, k) 133 msg.Headers[canonK] = vals 134 } 135 136 return nil 137 } 138 139 func (m *mailImpl) sendImpl(msg *mail.Message) error { 140 email := "" 141 if u := user.Current(m); u != nil { 142 email = u.Email 143 } 144 145 m.data.Lock() 146 adminsPlain := m.data.adminsPlain[:] 147 m.data.Unlock() 148 149 testMsg := &mail.TestMessage{Message: *msg} 150 151 if err := checkMessage(testMsg, adminsPlain, email); err != nil { 152 return err 153 } 154 m.data.Lock() 155 m.data.queue = append(m.data.queue, testMsg) 156 m.data.Unlock() 157 return nil 158 } 159 160 func (m *mailImpl) Send(msg *mail.Message) error { 161 return m.sendImpl(msg.Copy()) 162 } 163 164 func (m *mailImpl) SendToAdmins(msg *mail.Message) error { 165 msg = msg.Copy() 166 m.data.Lock() 167 ads := m.data.admins[:] 168 m.data.Unlock() 169 170 msg.To = make([]string, len(ads)) 171 copy(msg.To, ads) 172 173 return m.sendImpl(msg) 174 } 175 176 func (m *mailImpl) GetTestable() mail.Testable { return m } 177 178 func (m *mailImpl) SetAdminEmails(emails ...string) { 179 adminsPlain := make([]string, len(emails)) 180 for i, e := range emails { 181 adr, err := net_mail.ParseAddress(e) 182 if err != nil { 183 panic(fmt.Errorf("invalid email (%q): %s", e, err)) 184 } 185 adminsPlain[i] = adr.Address 186 } 187 188 m.data.Lock() 189 m.data.admins = emails 190 m.data.adminsPlain = adminsPlain 191 m.data.Unlock() 192 } 193 194 func (m *mailImpl) SentMessages() []*mail.TestMessage { 195 m.data.Lock() 196 msgs := m.data.queue[:] 197 m.data.Unlock() 198 199 ret := make([]*mail.TestMessage, len(msgs)) 200 for i, m := range msgs { 201 ret[i] = m.Copy() 202 } 203 return ret 204 } 205 206 func (m *mailImpl) Reset() { 207 m.data.Lock() 208 m.data.queue = nil 209 m.data.Unlock() 210 }