1717import asyncio
1818import datetime
1919import logging
20+ import re
2021from dataclasses import dataclass
2122from typing import TYPE_CHECKING , List , Optional
2223
3435
3536logger = logging .getLogger (__name__ )
3637
38+ VALID_KEY_RE = re .compile (r'^[-/_=\.a-zA-Z0-9]+$' )
39+
40+
41+ def _is_key_valid (key : str ) -> bool :
42+ if len (key ) == 0 or key [0 ] == '.' or key [- 1 ] == '.' :
43+ return False
44+ return bool (VALID_KEY_RE .match (key ))
45+
3746
3847class KeyValue :
3948 """
@@ -124,10 +133,18 @@ def __init__(
124133 self ._js = js
125134 self ._direct = direct
126135
127- async def get (self , key : str , revision : Optional [int ] = None ) -> Entry :
136+ async def get (
137+ self ,
138+ key : str ,
139+ revision : Optional [int ] = None ,
140+ validate_keys : bool = True
141+ ) -> Entry :
128142 """
129143 get returns the latest value for the key.
130144 """
145+ if validate_keys and not _is_key_valid (key ):
146+ raise nats .js .errors .InvalidKeyError
147+
131148 entry = None
132149 try :
133150 entry = await self ._get (key , revision )
@@ -179,21 +196,33 @@ async def _get(self, key: str, revision: Optional[int] = None) -> Entry:
179196
180197 return entry
181198
182- async def put (self , key : str , value : bytes ) -> int :
199+ async def put (
200+ self , key : str , value : bytes , validate_keys : bool = True
201+ ) -> int :
183202 """
184203 put will place the new value for the key into the store
185204 and return the revision number.
186205 """
206+ if validate_keys and not _is_key_valid (key ):
207+ raise nats .js .errors .InvalidKeyError (key )
208+
187209 pa = await self ._js .publish (f"{ self ._pre } { key } " , value )
188210 return pa .seq
189211
190- async def create (self , key : str , value : bytes ) -> int :
212+ async def create (
213+ self , key : str , value : bytes , validate_keys : bool = True
214+ ) -> int :
191215 """
192216 create will add the key/value pair iff it does not exist.
193217 """
218+ if validate_keys and not _is_key_valid (key ):
219+ raise nats .js .errors .InvalidKeyError (key )
220+
194221 pa = None
195222 try :
196- pa = await self .update (key , value , last = 0 )
223+ pa = await self .update (
224+ key , value , last = 0 , validate_keys = validate_keys
225+ )
197226 except nats .js .errors .KeyWrongLastSequenceError as err :
198227 # In case of attempting to recreate an already deleted key,
199228 # the client would get a KeyWrongLastSequenceError. When this happens,
@@ -213,16 +242,28 @@ async def create(self, key: str, value: bytes) -> int:
213242 # to recreate using the last revision.
214243 raise err
215244 except nats .js .errors .KeyDeletedError as err :
216- pa = await self .update (key , value , last = err .entry .revision )
245+ pa = await self .update (
246+ key ,
247+ value ,
248+ last = err .entry .revision ,
249+ validate_keys = validate_keys
250+ )
217251
218252 return pa
219253
220254 async def update (
221- self , key : str , value : bytes , last : Optional [int ] = None
255+ self ,
256+ key : str ,
257+ value : bytes ,
258+ last : Optional [int ] = None ,
259+ validate_keys : bool = True
222260 ) -> int :
223261 """
224262 update will update the value iff the latest revision matches.
225263 """
264+ if validate_keys and not _is_key_valid (key ):
265+ raise nats .js .errors .InvalidKeyError (key )
266+
226267 hdrs = {}
227268 if not last :
228269 last = 0
@@ -243,10 +284,18 @@ async def update(
243284 raise err
244285 return pa .seq
245286
246- async def delete (self , key : str , last : Optional [int ] = None ) -> bool :
287+ async def delete (
288+ self ,
289+ key : str ,
290+ last : Optional [int ] = None ,
291+ validate_keys : bool = True
292+ ) -> bool :
247293 """
248294 delete will place a delete marker and remove all previous revisions.
249295 """
296+ if validate_keys and not _is_key_valid (key ):
297+ raise nats .js .errors .InvalidKeyError (key )
298+
250299 hdrs = {}
251300 hdrs [KV_OP ] = KV_DEL
252301
0 commit comments