@@ -698,6 +698,8 @@ public MUCRole joinRoom( @Nonnull String nickname,
698
698
final Presence presenceItemNotFound = new Presence (Presence .Type .error );
699
699
presenceItemNotFound .setError (PacketError .Condition .item_not_found );
700
700
presenceItemNotFound .setFrom (role .getRoleAddress ());
701
+
702
+ // Not needed to create a defensive copy of the stanza. It's not used anywhere else.
701
703
joinRole .send (presenceItemNotFound );
702
704
}
703
705
@@ -722,7 +724,10 @@ private void sendRoomHistoryAfterJoin( @Nonnull final LocalMUCUser user, @Nonnul
722
724
Log .trace ( "Sending default room history to user '{}' that joined room '{}'." , user .getAddress (), this .getJID () );
723
725
final Iterator <Message > history = roomHistory .getMessageHistory ();
724
726
while (history .hasNext ()) {
725
- joinRole .send (history .next ());
727
+ // OF-2163: Prevent modifying the original history stanza (that can be retrieved by others later) by making a defensive copy.
728
+ // This prevents the stanzas in the room history to have a 'to' address for the last user that it was sent to.
729
+ final Message message = history .next ().createCopy ();
730
+ joinRole .send (message );
726
731
}
727
732
} else {
728
733
Log .trace ( "Sending user-requested room history to user '{}' that joined room '{}'." , user .getAddress (), this .getJID () );
@@ -736,7 +741,10 @@ private void sendRoomHistoryAfterJoin( @Nonnull final LocalMUCUser user, @Nonnul
736
741
private void sendRoomSubjectAfterJoin ( @ Nonnull final LocalMUCUser user , @ Nonnull MUCRole joinRole )
737
742
{
738
743
Log .trace ( "Sending room subject to user '{}' that joined room '{}'." , user .getAddress (), this .getJID () );
739
- Message roomSubject = roomHistory .getChangedSubject ();
744
+
745
+ // OF-2163: Prevent modifying the original subject stanza (that can be retrieved by others later) by making a defensive copy.
746
+ // This prevents the stanza kept in memory to have the 'to' address for the last user that it was sent to.
747
+ Message roomSubject = roomHistory .getChangedSubject ().createCopy ();
740
748
741
749
// 7.2.15 If there is no subject set, the room MUST return an empty <subject/> element.
742
750
if (roomSubject == null ) {
@@ -1034,14 +1042,11 @@ void sendInitialPresencesToNewOccupant(MUCRole joinRole) {
1034
1042
}
1035
1043
}
1036
1044
1037
- final Presence occupantPresence ;
1045
+ final Presence occupantPresence = occupant . getPresence (). createCopy (); // defensive copy to prevent modifying the original.
1038
1046
if (!canAnyoneDiscoverJID () && MUCRole .Role .moderator != joinRole .getRole ()) {
1039
1047
// Don't include the occupant's JID if the room is semi-anon and the new occupant is not a moderator
1040
- occupantPresence = occupant .getPresence ().createCopy (); // defensive copy to prevent modifying the original.
1041
1048
final Element frag = occupantPresence .getChildElement ("x" , "http://jabber.org/protocol/muc#user" );
1042
1049
frag .element ("item" ).addAttribute ("jid" , null );
1043
- } else {
1044
- occupantPresence = occupant .getPresence ();
1045
1050
}
1046
1051
joinRole .send (occupantPresence );
1047
1052
}
@@ -1086,8 +1091,8 @@ CompletableFuture<Void> sendLeavePresenceToExistingOccupants(MUCRole leaveRole)
1086
1091
// Send the presence of this new occupant to existing occupants
1087
1092
Log .trace ( "Send presence of leaving occupant '{}' to existing occupants of room '{}'." , leaveRole .getUserAddress (), leaveRole .getChatRoom ().getJID () );
1088
1093
try {
1089
- Presence originalPresence = leaveRole .getPresence ();
1090
- Presence presence = originalPresence .createCopy ();
1094
+ final Presence originalPresence = leaveRole .getPresence ();
1095
+ final Presence presence = originalPresence .createCopy (); // Defensive copy to not modify the original.
1091
1096
presence .setType (Presence .Type .unavailable );
1092
1097
presence .setStatus (null );
1093
1098
// Change (or add) presence information about roles and affiliations
@@ -1326,13 +1331,12 @@ public void destroyRoom(DestroyRoomRequest destroyRequest) {
1326
1331
for (MUCRole removedRole : removedRoles ) {
1327
1332
try {
1328
1333
// Send a presence stanza of type "unavailable" to the occupant
1329
- Presence presence = createPresence (Presence .Type .unavailable );
1334
+ final Presence presence = createPresence (Presence .Type .unavailable );
1330
1335
presence .setFrom (removedRole .getRoleAddress ());
1331
1336
1332
1337
// A fragment containing the x-extension for room destruction.
1333
- Element fragment = presence .addChildElement ("x" ,
1334
- "http://jabber.org/protocol/muc#user" );
1335
- Element item = fragment .addElement ("item" );
1338
+ final Element fragment = presence .addChildElement ("x" ,"http://jabber.org/protocol/muc#user" );
1339
+ final Element item = fragment .addElement ("item" );
1336
1340
item .addAttribute ("affiliation" , "none" );
1337
1341
item .addAttribute ("role" , "none" );
1338
1342
if (alternateJID != null ) {
@@ -1345,6 +1349,8 @@ public void destroyRoom(DestroyRoomRequest destroyRequest) {
1345
1349
}
1346
1350
destroy .addElement ("reason" ).setText (reason );
1347
1351
}
1352
+
1353
+ // Not needed to create a defensive copy of the stanza. It's not used anywhere else.
1348
1354
removedRole .send (presence );
1349
1355
}
1350
1356
catch (Exception e ) {
@@ -1418,16 +1424,24 @@ public void sendPrivatePacket(Packet packet, MUCRole senderRole) throws NotFound
1418
1424
if (occupants == null || occupants .size () == 0 ) {
1419
1425
throw new NotFoundException ();
1420
1426
}
1421
- if (canAnyoneDiscoverJID && packet instanceof Message ) {
1422
- addRealJidToMessage ((Message )packet , senderRole );
1427
+
1428
+ // OF-2163: Prevent modifying the original stanza (that can be used by unrelated code, after this method returns) by making a defensive copy.
1429
+ final Packet stanza = packet .createCopy ();
1430
+ if (canAnyoneDiscoverJID && stanza instanceof Message ) {
1431
+ addRealJidToMessage ((Message )stanza , senderRole );
1423
1432
}
1424
- for (MUCRole occupant : occupants ) {
1425
- packet .setFrom (senderRole .getRoleAddress ());
1426
- occupant .send (packet );
1427
- if (packet instanceof Message ) {
1428
- Message message = (Message ) packet ;
1429
- MUCEventDispatcher .privateMessageRecieved (occupant .getUserAddress (), senderRole .getUserAddress (),
1430
- message );
1433
+ stanza .setFrom (senderRole .getRoleAddress ());
1434
+
1435
+ // Sending the stanza will modify it. Make sure that the event listeners that are triggered after sending
1436
+ // the stanza don't get the 'real' address from the recipient.
1437
+ final Packet immutable = stanza .createCopy ();
1438
+
1439
+ // Forward it to each occupant.
1440
+ for (final MUCRole occupant : occupants ) {
1441
+ occupant .send (stanza ); // Use the stanza copy to send data. The 'to' address of this object will be changed by sending it.
1442
+ if (stanza instanceof Message ) {
1443
+ // Use an unmodified copy of the stanza (with the original 'to' address) when invoking event listeners (OF-2163)
1444
+ MUCEventDispatcher .privateMessageRecieved (occupant .getUserAddress (), senderRole .getUserAddress (), (Message ) immutable );
1431
1445
}
1432
1446
}
1433
1447
}
@@ -1491,28 +1505,31 @@ private CompletableFuture<Void> broadcastPresence( Presence presence, boolean is
1491
1505
throw new IllegalArgumentException ("Broadcasted presence stanza's 'from' JID " + presence .getFrom () + " does not match room JID: " + this .getJID ());
1492
1506
}
1493
1507
1508
+ // Create a defensive copy, to prevent modifications to leak back to the invoker.
1509
+ final Presence stanza = presence .createCopy ();
1510
+
1494
1511
// Some clients send a presence update to the room, rather than to their own nickname.
1495
- if ( JiveGlobals .getBooleanProperty ("xmpp.muc.presence.overwrite-to-room" , true ) && presence .getTo () != null && presence .getTo ().getResource () == null && sender .getRoleAddress () != null ) {
1496
- presence .setTo ( sender .getRoleAddress () );
1512
+ if ( JiveGlobals .getBooleanProperty ("xmpp.muc.presence.overwrite-to-room" , true ) && stanza .getTo () != null && stanza .getTo ().getResource () == null && sender .getRoleAddress () != null ) {
1513
+ stanza .setTo ( sender .getRoleAddress () );
1497
1514
}
1498
1515
1499
- if (!shouldBroadcastPresence (presence )) {
1516
+ if (!shouldBroadcastPresence (stanza )) {
1500
1517
// Just send the presence to the sender of the presence
1501
- sender .send (presence );
1518
+ sender .send (stanza );
1502
1519
return CompletableFuture .completedFuture (null );
1503
1520
}
1504
1521
1505
1522
// If FMUC is active, propagate the presence through FMUC first. Note that when a master-slave mode is active,
1506
1523
// we need to wait for an echo back, before the message can be broadcasted locally. The 'propagate' method will
1507
1524
// return a CompletableFuture object that is completed as soon as processing can continue.
1508
- return fmucHandler .propagate (presence , sender )
1525
+ return fmucHandler .propagate (stanza , sender )
1509
1526
.thenRunAsync (() -> {
1510
1527
// Broadcast presence to occupants hosted by other cluster nodes
1511
- BroadcastPresenceRequest request = new BroadcastPresenceRequest (this , sender , presence , isJoinPresence );
1528
+ BroadcastPresenceRequest request = new BroadcastPresenceRequest (this , sender , stanza , isJoinPresence );
1512
1529
CacheFactory .doClusterTask (request );
1513
1530
1514
1531
// Broadcast presence to occupants connected to this JVM
1515
- request = new BroadcastPresenceRequest (this , sender , presence , isJoinPresence );
1532
+ request = new BroadcastPresenceRequest (this , sender , stanza , isJoinPresence );
1516
1533
request .setOriginator (true );
1517
1534
request .run ();
1518
1535
}
@@ -1687,14 +1704,18 @@ public void broadcast(@Nonnull final BroadcastMessageRequest messageRequest)
1687
1704
// Add message to the room history
1688
1705
roomHistory .addMessage (message );
1689
1706
// Send message to occupants connected to this JVM
1690
- for (MUCRole occupant : occupantsByFullJID .values ()) {
1707
+
1708
+ // Create a defensive copy of the message that will be broadcast, as the broadcast will modify it ('to' addresses
1709
+ // will be changed), and it's undesirable to see these modifications in post-processing (OF-2163).
1710
+ final Message mutatingCopy = message .createCopy ();
1711
+ for (final MUCRole occupant : occupantsByFullJID .values ()) {
1691
1712
try
1692
1713
{
1693
1714
// Do not send broadcast messages to deaf occupants or occupants hosted in
1694
1715
// other cluster nodes or other FMUC nodes.
1695
1716
if ( occupant .isLocal () && !occupant .isVoiceOnly () && !occupant .isRemoteFmuc () )
1696
1717
{
1697
- occupant .send ( message );
1718
+ occupant .send ( mutatingCopy );
1698
1719
}
1699
1720
}
1700
1721
catch ( Exception e )
@@ -2837,8 +2858,11 @@ private void kickPresence(Presence kickPresence, JID actorJID, String nick) {
2837
2858
actor .addAttribute ("nick" , nick );
2838
2859
}
2839
2860
}
2840
- // Send the unavailable presence to the banned user
2841
- kickedRole .send (kickPresence );
2861
+
2862
+ // Send a defensive copy (to not leak a change to the 'to' address - this is possibly overprotective here,
2863
+ // but we're erring on the side of caution) of the unavailable presence to the banned user.
2864
+ kickedRole .send (kickPresence .createCopy ());
2865
+
2842
2866
// Remove the occupant from the room's occupants lists
2843
2867
OccupantLeftEvent event = new OccupantLeftEvent (this , kickedRole );
2844
2868
event .setOriginator (true );
0 commit comments