mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	messages: Return shallow copy of message object.
When more than one outgoing webhook is configured, the message which is send to the webhook bot passes through finalize_payload function multiple times, which mutated the message dict in a way that many keys were lost from the dict obj. This commit fixes that problem by having `finalize_payload` return a shallow copy of the incoming dict, instead of mutating it. We still mutate dicts inside of `post_process_dicts`, though, for performance reasons. This was slightly modified by @showell to fix the `test_both_codepaths` test that was added concurrently to this work. (I used a slightly verbose style in the tests to emphasize the transformation from `wide_dict` to `narrow_dict`.) I also removed a deepcopy call inside `get_client_payload`, since we now no longer mutate in `finalize_payload`. Finally, I added some comments here and there. For testing, I mostly protect against the root cause of the bug happening again, by adding a line to make sure that `sender_realm_id` does not get wiped out from the "wide" dictionary. A better test would exercise the actual code that exposed the bug here by sending a message to a bot with two or more services attached to it. I will do that in a future commit. Fixes #14384
This commit is contained in:
		@@ -2,6 +2,7 @@ import datetime
 | 
			
		||||
import ujson
 | 
			
		||||
import zlib
 | 
			
		||||
import ahocorasick
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
from django.utils.timezone import now as timezone_now
 | 
			
		||||
@@ -190,17 +191,46 @@ class MessageDict:
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def post_process_dicts(objs: List[Dict[str, Any]], apply_markdown: bool, client_gravatar: bool) -> None:
 | 
			
		||||
        '''
 | 
			
		||||
        NOTE: This function mutates the objects in
 | 
			
		||||
              the `objs` list, rather than making
 | 
			
		||||
              shallow copies.  It might be safer to
 | 
			
		||||
              make shallow copies here, but performance
 | 
			
		||||
              is somewhat important here, as we are
 | 
			
		||||
              often fetching several messages.
 | 
			
		||||
        '''
 | 
			
		||||
        MessageDict.bulk_hydrate_sender_info(objs)
 | 
			
		||||
        MessageDict.bulk_hydrate_recipient_info(objs)
 | 
			
		||||
 | 
			
		||||
        for obj in objs:
 | 
			
		||||
            MessageDict.finalize_payload(obj, apply_markdown, client_gravatar)
 | 
			
		||||
            MessageDict._finalize_payload(obj, apply_markdown, client_gravatar)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def finalize_payload(obj: Dict[str, Any],
 | 
			
		||||
                         apply_markdown: bool,
 | 
			
		||||
                         client_gravatar: bool,
 | 
			
		||||
                         keep_rendered_content: bool=False) -> None:
 | 
			
		||||
                         keep_rendered_content: bool=False) -> Dict[str, Any]:
 | 
			
		||||
        '''
 | 
			
		||||
        Make a shallow copy of the incoming dict to avoid
 | 
			
		||||
        mutation-related bugs.  This function is often
 | 
			
		||||
        called when we're sending out message events to
 | 
			
		||||
        multiple clients, who often want the final dictionary
 | 
			
		||||
        to have different shapes here based on the parameters.
 | 
			
		||||
        '''
 | 
			
		||||
        new_obj = copy.copy(obj)
 | 
			
		||||
 | 
			
		||||
        # Next call our worker, which mutates the record in place.
 | 
			
		||||
        MessageDict._finalize_payload(
 | 
			
		||||
            new_obj,
 | 
			
		||||
            apply_markdown=apply_markdown,
 | 
			
		||||
            client_gravatar=client_gravatar,
 | 
			
		||||
            keep_rendered_content=keep_rendered_content
 | 
			
		||||
        )
 | 
			
		||||
        return new_obj
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _finalize_payload(obj: Dict[str, Any], apply_markdown: bool, client_gravatar: bool,
 | 
			
		||||
                          keep_rendered_content: bool=False) -> None:
 | 
			
		||||
        MessageDict.set_sender_avatar(obj, client_gravatar)
 | 
			
		||||
        if apply_markdown:
 | 
			
		||||
            obj['content_type'] = 'text/html'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user