@@ -46,13 +46,33 @@ def wrapped(*args, **kwargs):
4646 return wrapped
4747
4848
49- def decrypt_cryptojs_aes (data ):
49+ def decrypt_cryptojs_aes_stores (data ):
5050 encrypted_stores = data ['context' ]['dispatcher' ]['stores' ]
51- _cs = data ["_cs" ]
52- _cr = data ["_cr" ]
5351
54- _cr = b"" .join (int .to_bytes (i , length = 4 , byteorder = "big" , signed = True ) for i in json .loads (_cr )["words" ])
55- password = hashlib .pbkdf2_hmac ("sha1" , _cs .encode ("utf8" ), _cr , 1 , dklen = 32 ).hex ()
52+ if "_cs" in data and "_cr" in data :
53+ _cs = data ["_cs" ]
54+ _cr = data ["_cr" ]
55+ _cr = b"" .join (int .to_bytes (i , length = 4 , byteorder = "big" , signed = True ) for i in json .loads (_cr )["words" ])
56+ password = hashlib .pbkdf2_hmac ("sha1" , _cs .encode ("utf8" ), _cr , 1 , dklen = 32 ).hex ()
57+ else :
58+ # Currently assume one extra key in dict, which is password. Print error if
59+ # more extra keys detected.
60+ new_keys = [k for k in data .keys () if k not in ["context" , "plugins" ]]
61+ l = len (new_keys )
62+ if l == 0 :
63+ return None
64+ elif l == 1 and isinstance (data [new_keys [0 ]], str ):
65+ password_key = new_keys [0 ]
66+ else :
67+ msg = "Yahoo has again changed data format, yfinance now unsure which key(s) is for decryption:"
68+ k = new_keys [0 ]
69+ k_str = k if len (k ) < 32 else k [:32 - 3 ]+ "..."
70+ msg += f" '{ k_str } '->{ type (data [k ])} "
71+ for i in range (1 , len (new_keys )):
72+ msg += f" , '{ k_str } '->{ type (data [k ])} "
73+ raise Exception (msg )
74+ password_key = new_keys [0 ]
75+ password = data [password_key ]
5676
5777 encrypted_stores = b64decode (encrypted_stores )
5878 assert encrypted_stores [0 :8 ] == b"Salted__"
@@ -98,7 +118,10 @@ def EVPKDF(password, salt, keySize=32, ivSize=16, iterations=1, hashAlgorithm="m
98118 key , iv = key_iv [:keySize ], key_iv [keySize :final_length ]
99119 return key , iv
100120
101- key , iv = EVPKDF (password , salt , keySize = 32 , ivSize = 16 , iterations = 1 , hashAlgorithm = "md5" )
121+ try :
122+ key , iv = EVPKDF (password , salt , keySize = 32 , ivSize = 16 , iterations = 1 , hashAlgorithm = "md5" )
123+ except :
124+ raise Exception ("yfinance failed to decrypt Yahoo data response" )
102125
103126 if usePycryptodome :
104127 cipher = AES .new (key , AES .MODE_CBC , iv = iv )
@@ -176,15 +199,16 @@ def get_json_data_stores(self, sub_page: str = None, proxy=None) -> dict:
176199
177200 data = json .loads (json_str )
178201
179- if "_cs" in data and "_cr" in data :
180- data = decrypt_cryptojs_aes (data )
181-
182- if "context" in data and "dispatcher" in data ["context" ]:
183- # Keep old code, just in case
184- data = data ['context' ]['dispatcher' ]['stores' ]
202+ stores = decrypt_cryptojs_aes_stores (data )
203+ if stores is None :
204+ # Maybe Yahoo returned old format, not encrypted
205+ if "context" in data and "dispatcher" in data ["context" ]:
206+ stores = data ['context' ]['dispatcher' ]['stores' ]
207+ if stores is None :
208+ raise Exception (f"{ self .ticker } : Failed to extract data stores from web request" )
185209
186210 # return data
187- new_data = json .dumps (data ).replace ('{}' , 'null' )
211+ new_data = json .dumps (stores ).replace ('{}' , 'null' )
188212 new_data = re .sub (
189213 r'{[\'|\"]raw[\'|\"]:(.*?),(.*?)}' , r'\1' , new_data )
190214
0 commit comments