66
77# Copyright (c) Microsoft Corporation. All rights reserved.
88# Licensed under the MIT License.
9-
9+ from hashlib import sha256
1010from typing import Dict , List
1111from threading import Semaphore
1212import json
@@ -42,6 +42,42 @@ def __init__(self, endpoint: str = None, masterkey: str = None, database: str =
4242 self .container_creation_options = container_creation_options or kwargs .get ('container_creation_options' )
4343
4444
45+ class CosmosDbKeyEscape :
46+
47+ @staticmethod
48+ def sanitize_key (key ) -> str :
49+ """Return the sanitized key.
50+
51+ Replace characters that are not allowed in keys in Cosmos.
52+
53+ :param key:
54+ :return str:
55+ """
56+ # forbidden characters
57+ bad_chars = ['\\ ' , '?' , '/' , '#' , '\t ' , '\n ' , '\r ' , '*' ]
58+ # replace those with with '*' and the
59+ # Unicode code point of the character and return the new string
60+ key = '' .join (
61+ map (
62+ lambda x : '*' + str (ord (x )) if x in bad_chars else x , key
63+ )
64+ )
65+
66+ return CosmosDbKeyEscape .truncate_key (key )
67+
68+ @staticmethod
69+ def truncate_key (key : str ) -> str :
70+ MAX_KEY_LEN = 255
71+
72+ if len (key ) > MAX_KEY_LEN :
73+ aux_hash = sha256 (key .encode ('utf-8' ))
74+ aux_hex = aux_hash .hexdigest ()
75+
76+ key = key [0 :MAX_KEY_LEN - len (aux_hex )] + aux_hex
77+
78+ return key
79+
80+
4581class CosmosDbStorage (Storage ):
4682 """The class for CosmosDB middleware for the Azure Bot Framework."""
4783
@@ -77,7 +113,7 @@ async def read(self, keys: List[str]) -> Dict[str, object]:
77113 if keys :
78114 # create the parameters object
79115 parameters = [
80- {'name' : f'@id{ i } ' , 'value' : f'{ self . __sanitize_key (key )} ' }
116+ {'name' : f'@id{ i } ' , 'value' : f'{ CosmosDbKeyEscape . sanitize_key (key )} ' }
81117 for i , key in enumerate (keys )
82118 ]
83119 # get the names of the params
@@ -125,7 +161,7 @@ async def write(self, changes: Dict[str, StoreItem]):
125161 # store the e_tag
126162 e_tag = change .e_tag
127163 # create the new document
128- doc = {'id' : self . __sanitize_key (key ),
164+ doc = {'id' : CosmosDbKeyEscape . sanitize_key (key ),
129165 'realId' : key ,
130166 'document' : self .__create_dict (change )
131167 }
@@ -141,7 +177,7 @@ async def write(self, changes: Dict[str, StoreItem]):
141177 access_condition = {'type' : 'IfMatch' , 'condition' : e_tag }
142178 self .client .ReplaceItem (
143179 document_link = self .__item_link (
144- self . __sanitize_key (key )),
180+ CosmosDbKeyEscape . sanitize_key (key )),
145181 new_document = doc ,
146182 options = {'accessCondition' : access_condition }
147183 )
@@ -169,7 +205,7 @@ async def delete(self, keys: List[str]):
169205 # call the function for each key
170206 for k in keys :
171207 self .client .DeleteItem (
172- document_link = self .__item_link (self . __sanitize_key (k )),
208+ document_link = self .__item_link (CosmosDbKeyEscape . sanitize_key (k )),
173209 options = options
174210 )
175211 # print(res)
@@ -209,24 +245,6 @@ def __create_dict(self, si: StoreItem) -> Dict:
209245 return ({attr : getattr (si , attr )
210246 for attr in non_magic_attr })
211247
212- def __sanitize_key (self , key ) -> str :
213- """Return the sanitized key.
214-
215- Replace characters that are not allowed in keys in Cosmos.
216-
217- :param key:
218- :return str:
219- """
220- # forbidden characters
221- bad_chars = ['\\ ' , '?' , '/' , '#' , '\t ' , '\n ' , '\r ' ]
222- # replace those with with '*' and the
223- # Unicode code point of the character and return the new string
224- return '' .join (
225- map (
226- lambda x : '*' + str (ord (x )) if x in bad_chars else x , key
227- )
228- )
229-
230248 def __item_link (self , id ) -> str :
231249 """Return the item link of a item in the container.
232250
0 commit comments