@@ -66,8 +66,36 @@ pub struct MimeFactory {
66
66
67
67
selfstatus : String ,
68
68
69
- /// Vector of pairs of recipient name and address
70
- recipients : Vec < ( String , String ) > ,
69
+ /// Vector of actual recipient addresses.
70
+ ///
71
+ /// This is the list of addresses the message should be sent to.
72
+ /// It is not the same as the `To` header,
73
+ /// because in case of "member removed" message
74
+ /// removed member is in the recipient list,
75
+ /// but not in the `To` header.
76
+ /// In case of broadcast lists there are multiple recipients,
77
+ /// but the `To` header has no members.
78
+ ///
79
+ /// If `bcc_self` configuration is enabled,
80
+ /// this list will be extended with own address later,
81
+ /// but `MimeFactory` is not responsible for this.
82
+ recipients : Vec < String > ,
83
+
84
+ /// Vector of pairs of recipient name and address that goes into the `To` field.
85
+ ///
86
+ /// The list of actual message recipient addresses may be different,
87
+ /// e.g. if members are hidden for broadcast lists.
88
+ to : Vec < ( String , String ) > ,
89
+
90
+ /// Vector of pairs of past group member names and addresses.
91
+ past_members : Vec < ( String , String ) > ,
92
+
93
+ /// Timestamps of the members in the same order as in the `recipients`
94
+ /// followed by `past_members`.
95
+ ///
96
+ /// If this is not empty, its length
97
+ /// should be the sum of `recipients` and `past_members` length.
98
+ member_timestamps : Vec < i64 > ,
71
99
72
100
timestamp : i64 ,
73
101
loaded : Loaded ,
@@ -128,6 +156,7 @@ impl MimeFactory {
128
156
pub async fn from_msg ( context : & Context , msg : Message ) -> Result < MimeFactory > {
129
157
let chat = Chat :: load_from_db ( context, msg. chat_id ) . await ?;
130
158
let attach_profile_data = Self :: should_attach_profile_data ( & msg) ;
159
+ let undisclosed_recipients = chat. typ == Chattype :: Broadcast ;
131
160
132
161
let from_addr = context. get_primary_self_addr ( ) . await ?;
133
162
let config_displayname = context
@@ -145,47 +174,101 @@ impl MimeFactory {
145
174
( name, None )
146
175
} ;
147
176
148
- let mut recipients = Vec :: with_capacity ( 5 ) ;
177
+ let mut recipients = Vec :: new ( ) ;
178
+ let mut to = Vec :: new ( ) ;
179
+ let mut past_members = Vec :: new ( ) ;
180
+ let mut member_timestamps = Vec :: new ( ) ;
149
181
let mut recipient_ids = HashSet :: new ( ) ;
150
182
let mut req_mdn = false ;
151
183
152
184
if chat. is_self_talk ( ) {
153
185
if msg. param . get_cmd ( ) == SystemMessage :: AutocryptSetupMessage {
154
- recipients. push ( ( from_displayname. to_string ( ) , from_addr. to_string ( ) ) ) ;
186
+ recipients. push ( from_addr. to_string ( ) ) ;
187
+ to. push ( ( from_displayname. to_string ( ) , from_addr. to_string ( ) ) ) ;
155
188
}
156
189
} else if chat. is_mailing_list ( ) {
157
190
let list_post = chat
158
191
. param
159
192
. get ( Param :: ListPost )
160
193
. context ( "Can't write to mailinglist without ListPost param" ) ?;
161
- recipients. push ( ( "" . to_string ( ) , list_post. to_string ( ) ) ) ;
194
+ to. push ( ( "" . to_string ( ) , list_post. to_string ( ) ) ) ;
195
+ recipients. push ( list_post. to_string ( ) ) ;
162
196
} else {
197
+ let email_to_remove = if msg. param . get_cmd ( ) == SystemMessage :: MemberRemovedFromGroup {
198
+ msg. param . get ( Param :: Arg )
199
+ } else {
200
+ None
201
+ } ;
202
+
163
203
context
164
204
. sql
165
205
. query_map (
166
- "SELECT c.authname, c.addr, c.id \
167
- FROM chats_contacts cc \
168
- LEFT JOIN contacts c ON cc.contact_id=c.id \
169
- WHERE cc.chat_id=? AND cc.contact_id>9; ",
170
- ( msg. chat_id , ) ,
206
+ "SELECT c.authname, c.addr, c.id, cc.add_timestamp, cc.remove_timestamp
207
+ FROM chats_contacts cc
208
+ LEFT JOIN contacts c ON cc.contact_id=c.id
209
+ WHERE cc.chat_id=? AND cc.contact_id>9 OR (cc.contact_id=1 AND ?) " ,
210
+ ( msg. chat_id , chat . typ == Chattype :: Group ) ,
171
211
|row| {
172
212
let authname: String = row. get ( 0 ) ?;
173
213
let addr: String = row. get ( 1 ) ?;
174
214
let id: ContactId = row. get ( 2 ) ?;
175
- Ok ( ( authname, addr, id) )
215
+ let add_timestamp: i64 = row. get ( 3 ) ?;
216
+ let remove_timestamp: i64 = row. get ( 4 ) ?;
217
+ Ok ( ( authname, addr, id, add_timestamp, remove_timestamp) )
176
218
} ,
177
219
|rows| {
220
+ let mut past_member_timestamps = Vec :: new ( ) ;
221
+
178
222
for row in rows {
179
- let ( authname, addr, id) = row?;
180
- if !recipients_contain_addr ( & recipients, & addr) {
181
- let name = match attach_profile_data {
182
- true => authname,
183
- false => "" . to_string ( ) ,
184
- } ;
185
- recipients. push ( ( name, addr) ) ;
223
+ let ( authname, addr, id, add_timestamp, remove_timestamp) = row?;
224
+ let addr = if id == ContactId :: SELF {
225
+ from_addr. to_string ( )
226
+ } else {
227
+ addr
228
+ } ;
229
+ let name = match attach_profile_data {
230
+ true => authname,
231
+ false => "" . to_string ( ) ,
232
+ } ;
233
+ if add_timestamp >= remove_timestamp {
234
+ if !recipients_contain_addr ( & to, & addr) {
235
+ recipients. push ( addr. clone ( ) ) ;
236
+ if !undisclosed_recipients {
237
+ to. push ( ( name, addr) ) ;
238
+ member_timestamps. push ( add_timestamp) ;
239
+ }
240
+ }
241
+ recipient_ids. insert ( id) ;
242
+ } else {
243
+ // Row is a tombstone,
244
+ // member is not actually part of the group.
245
+ if !recipients_contain_addr ( & past_members, & addr) {
246
+ if let Some ( email_to_remove) = email_to_remove {
247
+ if email_to_remove == addr {
248
+ // This is a "member removed" message,
249
+ // we need to notify removed member
250
+ // that it was removed.
251
+ recipients. push ( addr. clone ( ) ) ;
252
+ }
253
+ }
254
+ if !undisclosed_recipients {
255
+ past_members. push ( ( name, addr) ) ;
256
+ past_member_timestamps. push ( remove_timestamp) ;
257
+ }
258
+ }
186
259
}
187
- recipient_ids. insert ( id) ;
188
260
}
261
+
262
+ debug_assert ! ( member_timestamps. len( ) >= to. len( ) ) ;
263
+
264
+ if to. len ( ) > 1 {
265
+ if let Some ( position) = to. iter ( ) . position ( |( _, x) | x == & from_addr) {
266
+ to. remove ( position) ;
267
+ member_timestamps. remove ( position) ;
268
+ }
269
+ }
270
+
271
+ member_timestamps. extend ( past_member_timestamps) ;
189
272
Ok ( ( ) )
190
273
} ,
191
274
)
@@ -226,12 +309,19 @@ impl MimeFactory {
226
309
} ;
227
310
let attach_selfavatar = Self :: should_attach_selfavatar ( context, & msg) . await ;
228
311
312
+ debug_assert ! (
313
+ member_timestamps. is_empty( )
314
+ || to. len( ) + past_members. len( ) == member_timestamps. len( )
315
+ ) ;
229
316
let factory = MimeFactory {
230
317
from_addr,
231
318
from_displayname,
232
319
sender_displayname,
233
320
selfstatus,
234
321
recipients,
322
+ to,
323
+ past_members,
324
+ member_timestamps,
235
325
timestamp : msg. timestamp_sort ,
236
326
loaded : Loaded :: Message { msg, chat } ,
237
327
in_reply_to,
@@ -259,7 +349,10 @@ impl MimeFactory {
259
349
from_displayname : "" . to_string ( ) ,
260
350
sender_displayname : None ,
261
351
selfstatus : "" . to_string ( ) ,
262
- recipients : vec ! [ ( "" . to_string( ) , contact. get_addr( ) . to_string( ) ) ] ,
352
+ recipients : vec ! [ contact. get_addr( ) . to_string( ) ] ,
353
+ to : vec ! [ ( "" . to_string( ) , contact. get_addr( ) . to_string( ) ) ] ,
354
+ past_members : vec ! [ ] ,
355
+ member_timestamps : vec ! [ ] ,
263
356
timestamp,
264
357
loaded : Loaded :: Mdn {
265
358
rfc724_mid,
@@ -283,11 +376,7 @@ impl MimeFactory {
283
376
let self_addr = context. get_primary_self_addr ( ) . await ?;
284
377
285
378
let mut res = Vec :: new ( ) ;
286
- for ( _, addr) in self
287
- . recipients
288
- . iter ( )
289
- . filter ( |( _, addr) | addr != & self_addr)
290
- {
379
+ for addr in self . recipients . iter ( ) . filter ( |& addr| * addr != self_addr) {
291
380
res. push ( ( Peerstate :: from_addr ( context, addr) . await ?, addr. clone ( ) ) ) ;
292
381
}
293
382
@@ -475,10 +564,7 @@ impl MimeFactory {
475
564
}
476
565
477
566
pub fn recipients ( & self ) -> Vec < String > {
478
- self . recipients
479
- . iter ( )
480
- . map ( |( _, addr) | addr. clone ( ) )
481
- . collect ( )
567
+ self . recipients . clone ( )
482
568
}
483
569
484
570
/// Consumes a `MimeFactory` and renders it into a message which is then stored in
@@ -488,46 +574,33 @@ impl MimeFactory {
488
574
489
575
let from = new_address_with_name ( & self . from_displayname , self . from_addr . clone ( ) ) ;
490
576
491
- let undisclosed_recipients = match & self . loaded {
492
- Loaded :: Message { chat, .. } => chat. typ == Chattype :: Broadcast ,
493
- Loaded :: Mdn { .. } => false ,
494
- } ;
495
-
496
577
let mut to = Vec :: new ( ) ;
497
- if undisclosed_recipients {
578
+ for ( name, addr) in & self . to {
579
+ if name. is_empty ( ) {
580
+ to. push ( Address :: new_mailbox ( addr. clone ( ) ) ) ;
581
+ } else {
582
+ to. push ( new_address_with_name ( name, addr. clone ( ) ) ) ;
583
+ }
584
+ }
585
+
586
+ let mut past_members = Vec :: new ( ) ; // Contents of `Chat-Group-Past-Members` header.
587
+ for ( name, addr) in & self . past_members {
588
+ if name. is_empty ( ) {
589
+ past_members. push ( Address :: new_mailbox ( addr. clone ( ) ) ) ;
590
+ } else {
591
+ past_members. push ( new_address_with_name ( name, addr. clone ( ) ) ) ;
592
+ }
593
+ }
594
+
595
+ debug_assert ! (
596
+ self . member_timestamps. is_empty( )
597
+ || to. len( ) + past_members. len( ) == self . member_timestamps. len( )
598
+ ) ;
599
+ if to. is_empty ( ) {
498
600
to. push ( Address :: new_group (
499
601
"hidden-recipients" . to_string ( ) ,
500
602
Vec :: new ( ) ,
501
603
) ) ;
502
- } else {
503
- let email_to_remove = match & self . loaded {
504
- Loaded :: Message { msg, .. } => {
505
- if msg. param . get_cmd ( ) == SystemMessage :: MemberRemovedFromGroup {
506
- msg. param . get ( Param :: Arg )
507
- } else {
508
- None
509
- }
510
- }
511
- Loaded :: Mdn { .. } => None ,
512
- } ;
513
-
514
- for ( name, addr) in & self . recipients {
515
- if let Some ( email_to_remove) = email_to_remove {
516
- if email_to_remove == addr {
517
- continue ;
518
- }
519
- }
520
-
521
- if name. is_empty ( ) {
522
- to. push ( Address :: new_mailbox ( addr. clone ( ) ) ) ;
523
- } else {
524
- to. push ( new_address_with_name ( name, addr. clone ( ) ) ) ;
525
- }
526
- }
527
-
528
- if to. is_empty ( ) {
529
- to. push ( from. clone ( ) ) ;
530
- }
531
604
}
532
605
533
606
// Start with Internet Message Format headers in the order of the standard example
@@ -540,6 +613,26 @@ impl MimeFactory {
540
613
headers. push ( Header :: new_with_value ( "Sender" . into ( ) , vec ! [ sender] ) . unwrap ( ) ) ;
541
614
}
542
615
headers. push ( Header :: new_with_value ( "To" . into ( ) , to. clone ( ) ) . unwrap ( ) ) ;
616
+ if !past_members. is_empty ( ) {
617
+ headers. push (
618
+ Header :: new_with_value ( "Chat-Group-Past-Members" . into ( ) , past_members. clone ( ) )
619
+ . unwrap ( ) ,
620
+ ) ;
621
+ }
622
+
623
+ if !self . member_timestamps . is_empty ( ) {
624
+ headers. push (
625
+ Header :: new_with_value (
626
+ "Chat-Group-Member-Timestamps" . into ( ) ,
627
+ self . member_timestamps
628
+ . iter ( )
629
+ . map ( |ts| ts. to_string ( ) )
630
+ . collect :: < Vec < String > > ( )
631
+ . join ( " " ) ,
632
+ )
633
+ . unwrap ( ) ,
634
+ ) ;
635
+ }
543
636
544
637
let subject_str = self . subject_str ( context) . await ?;
545
638
let encoded_subject = if subject_str
@@ -2461,8 +2554,9 @@ mod tests {
2461
2554
// Alice creates a group with Bob and Claire and then removes Bob.
2462
2555
let alice = TestContext :: new_alice ( ) . await ;
2463
2556
2557
+ let claire_addr =
"[email protected] " ;
2464
2558
let bob_id =
Contact :: create ( & alice
, "Bob" , "[email protected] " ) . await ?
;
2465
- let claire_id =
Contact :: create ( & alice
, "Claire" , "[email protected] " ) . await ?
;
2559
+ let claire_id = Contact :: create ( & alice, "Claire" , claire_addr ) . await ?;
2466
2560
2467
2561
let alice_chat_id = create_group_chat ( & alice, ProtectionStatus :: Unprotected , "foo" ) . await ?;
2468
2562
add_contact_to_chat ( & alice, alice_chat_id, bob_id) . await ?;
@@ -2478,10 +2572,17 @@ mod tests {
2478
2572
. get_first_header ( "To" )
2479
2573
. context ( "no To: header parsed" ) ?;
2480
2574
let to = addrparse_header ( to) ?;
2481
- let mailbox = to
2482
- . extract_single_info ( )
2483
- . context ( "to: field does not contain exactly one address" ) ?;
2484
- assert_eq ! ( mailbox
. addr
, "[email protected] " ) ;
2575
+ for to_addr in to. iter ( ) {
2576
+ match to_addr {
2577
+ mailparse:: MailAddr :: Single ( ref info) => {
2578
+ // Addresses should be of existing members (Alice and Bob) and not Claire.
2579
+ assert_ne ! ( info. addr, claire_addr) ;
2580
+ }
2581
+ mailparse:: MailAddr :: Group ( _) => {
2582
+ panic ! ( "Group addresses are not expected here" ) ;
2583
+ }
2584
+ }
2585
+ }
2485
2586
2486
2587
Ok ( ( ) )
2487
2588
}
0 commit comments