@@ -61,7 +61,7 @@ https://{}/confirm/{}",
61
61
token
62
62
) ;
63
63
64
- self . send ( email, subject, & body)
64
+ self . send ( & [ email] , subject, & body)
65
65
}
66
66
67
67
/// Attempts to send an ownership invitation.
@@ -83,7 +83,45 @@ or go to https://{domain}/me/pending-invites to manage all of your crate ownersh
83
83
domain = crate :: config:: domain_name( )
84
84
) ;
85
85
86
- self . send ( email, subject, & body)
86
+ self . send ( & [ email] , subject, & body)
87
+ }
88
+
89
+ /// Attempts to send a new crate version published notification to crate owners.
90
+ pub fn notify_owners (
91
+ & self ,
92
+ emails : & [ & str ] ,
93
+ crate_name : & str ,
94
+ crate_version : & str ,
95
+ publisher_name : Option < & str > ,
96
+ publisher_email : & str ,
97
+ ) -> AppResult < ( ) > {
98
+ let subject = format ! (
99
+ "Crate {} ({}) published to {}" ,
100
+ crate_name,
101
+ crate_version,
102
+ crate :: config:: domain_name( )
103
+ ) ;
104
+ let body = format ! (
105
+ "A crate you have publish access to has recently released a new version.
106
+ Crate: {} ({})
107
+ Published by: {} <{}>
108
+ Published at: {}
109
+ If this publish is expected, you do not need to take further action.
110
+ Only if this publish is unexpected, please take immediate steps to secure your account:
111
+ * If you suspect your GitHub account was compromised, change your password
112
+ * Revoke your API Token
113
+ * Yank the version of the crate reported in this email
114
+ * Report this incident to RustSec https://rustsec.org
115
+ To stop receiving these messages, update your email notification settings at https://{domain}/me" ,
116
+ crate_name,
117
+ crate_version,
118
+ publisher_name. unwrap_or( "(unknown username)" ) ,
119
+ publisher_email,
120
+ chrono:: Utc :: now( ) . to_rfc2822( ) ,
121
+ domain = crate :: config:: domain_name( )
122
+ ) ;
123
+
124
+ self . send ( emails, & subject, & body)
87
125
}
88
126
89
127
/// This is supposed to be used only during tests, to retrieve the messages stored in the
@@ -96,9 +134,19 @@ or go to https://{domain}/me/pending-invites to manage all of your crate ownersh
96
134
}
97
135
}
98
136
99
- fn send ( & self , recipient : & str , subject : & str , body : & str ) -> AppResult < ( ) > {
100
- let email = Message :: builder ( )
101
- . to ( recipient. parse ( ) ?)
137
+ fn send ( & self , recipients : & [ & str ] , subject : & str , body : & str ) -> AppResult < ( ) > {
138
+ let mut recipients_iter = recipients. iter ( ) ;
139
+
140
+ let mut builder = Message :: builder ( ) ;
141
+ let to = recipients_iter
142
+ . next ( )
143
+ . ok_or_else ( || server_error ( "Email has no recipients" ) ) ?;
144
+ builder = builder. to ( to. parse ( ) ?) ;
145
+ for bcc in recipients_iter {
146
+ builder = builder. bcc ( bcc. parse ( ) ?) ;
147
+ }
148
+
149
+ let email = builder
102
150
. from ( self . sender_address ( ) . parse ( ) ?)
103
151
. subject ( subject)
104
152
. body ( body. to_string ( ) ) ?;
@@ -122,7 +170,12 @@ or go to https://{domain}/me/pending-invites to manage all of your crate ownersh
122
170
. map_err ( |_| server_error ( "Email file could not be generated" ) ) ?;
123
171
}
124
172
EmailBackend :: Memory { mails } => mails. lock ( ) . unwrap ( ) . push ( StoredEmail {
125
- to : recipient. into ( ) ,
173
+ to : to. to_string ( ) ,
174
+ bcc : recipients
175
+ . iter ( )
176
+ . skip ( 1 )
177
+ . map ( |address| address. to_string ( ) )
178
+ . collect ( ) ,
126
179
subject : subject. into ( ) ,
127
180
body : body. into ( ) ,
128
181
} ) ,
@@ -176,6 +229,7 @@ impl std::fmt::Debug for EmailBackend {
176
229
#[ derive( Debug , Clone ) ]
177
230
pub struct StoredEmail {
178
231
pub to : String ,
232
+ pub bcc : Vec < String > ,
179
233
pub subject : String ,
180
234
pub body : String ,
181
235
}
@@ -189,7 +243,7 @@ mod tests {
189
243
let emails = Emails :: new_in_memory ( ) ;
190
244
191
245
assert_err ! ( emails. send(
192
- "String.Format(\" {0}.{1}@live.com\" , FirstName, LastName)" ,
246
+ & [ "String.Format(\" {0}.{1}@live.com\" , FirstName, LastName)" ] ,
193
247
"test" ,
194
248
"test" ,
195
249
) ) ;
@@ -199,6 +253,6 @@ mod tests {
199
253
fn sending_to_valid_email_succeeds ( ) {
200
254
let emails = Emails :: new_in_memory ( ) ;
201
255
202
- assert_ok ! ( emails
. send
( "[email protected] " , "test" , "test" ) ) ;
256
+ assert_ok ! ( emails
. send
( & [ "[email protected] " ] , "test" , "test" ) ) ;
203
257
}
204
258
}
0 commit comments