44# This source code is licensed under the license found in the
55# LICENSE file in the root directory of this source tree.
66
7+ import base64
8+ import os .path
79import random
810import re
911import time
1517from .util import EtldPlusOneResolver
1618
1719DEFAULT_1PC_AGE : Final [int ] = 90 * 24 * 3600 # 90 days
18- LANGUAGE_TOKEN : Final [str ] = "Ag"
20+ LANGUAGE_TOKEN : Final [str ] = "Ag" # Python
1921SUPPORTED_LANGUAGES_TOKENS : Final [List [str ]] = ["AQ" , "Ag" , "Aw" , "BA" , "BQ" , "Bg" ]
2022MIN_PAYLOAD_SPLIT_LENGTH : Final [int ] = 4
2123MAX_PAYLOAD_LENGTH_WITH_LANGUAGE_TOKEN : Final [int ] = 5
2527FBP_COOKIE_NAME : Final [str ] = "_fbp"
2628FBCLID_QUERY_PARAMS : Final [str ] = "fbclid"
2729
30+ # Appendix constants - matches JavaScript Constants.js
31+ DEFAULT_FORMAT : Final [int ] = 0x01
32+ LANGUAGE_TOKEN_INDEX : Final [int ] = 0x02 # Python language token index
33+ APPENDIX_LENGTH_V1 : Final [int ] = 2
34+ APPENDIX_LENGTH_V2 : Final [int ] = 8
35+
2836
2937class ParamBuilder :
3038 """
@@ -47,6 +55,10 @@ def __init__(self, input: Union[EtldPlusOneResolver, List, None] = None) -> None
4755 self .etld_plus_one : Optional [str ] = None
4856 self .domain_list : Optional [List ] = None
4957 self .etld_plus_one_resolver : Optional [EtldPlusOneResolver ] = None
58+ ## Appendix with version number
59+ self .appendix_new : str = self ._get_appendix (True )
60+ self .appendix_normal : str = self ._get_appendix (False )
61+
5062 if isinstance (input , List ):
5163 self .domain_list = []
5264 for domain in input :
@@ -55,6 +67,59 @@ def __init__(self, input: Union[EtldPlusOneResolver, List, None] = None) -> None
5567 elif isinstance (input , EtldPlusOneResolver ):
5668 self .etld_plus_one_resolver = input
5769
70+ def _get_version (self ) -> str :
71+ """
72+ Extract version from setup.py file
73+ """
74+ try :
75+ # Get the directory containing this Python file
76+ current_dir = os .path .dirname (os .path .abspath (__file__ ))
77+ # Navigate up to find setup.py
78+ setup_py_path = os .path .join (current_dir , ".." , ".." , "setup.py" )
79+ setup_py_path = os .path .normpath (setup_py_path )
80+
81+ if os .path .exists (setup_py_path ):
82+ with open (setup_py_path , "r" ) as f :
83+ content = f .read ()
84+ # Extract version using regex
85+ import re
86+
87+ version_match = re .search (
88+ r'version\s*=\s*["\']([^"\']+)["\']' , content
89+ )
90+ if version_match :
91+ return version_match .group (1 )
92+ # Fallback version if not found
93+ return "1.0.1"
94+ except Exception :
95+ # Fallback version on any error
96+ return "1.0.1"
97+
98+ def _get_appendix (self , is_new : bool ) -> str :
99+ version = self ._get_version ()
100+ version_parts = version .split ("." )
101+ major = int (version_parts [0 ])
102+ minor = int (version_parts [1 ])
103+ patch = int (version_parts [2 ])
104+
105+ is_new_byte = 0x01 if is_new else 0x00
106+
107+ bytes_array = [
108+ DEFAULT_FORMAT ,
109+ LANGUAGE_TOKEN_INDEX ,
110+ is_new_byte ,
111+ major ,
112+ minor ,
113+ patch ,
114+ ]
115+
116+ # Convert to bytes and then to base64url-safe string
117+ byte_data = bytes (bytes_array )
118+ base64_encoded = base64 .b64encode (byte_data ).decode ("ascii" )
119+ # Make it URL-safe by replacing characters
120+ base64url_safe = base64_encoded .replace ("+" , "-" ).replace ("/" , "_" ).rstrip ("=" )
121+ return base64url_safe
122+
58123 def _pre_process_cookies (
59124 self , cookies : dict [str , str ], cookie_name : str
60125 ) -> Optional [str ]:
@@ -69,15 +134,18 @@ def _pre_process_cookies(
69134 ):
70135 return None
71136
72- if (
73- len (cookie_split ) == MAX_PAYLOAD_LENGTH_WITH_LANGUAGE_TOKEN
74- and cookie_split [MAX_PAYLOAD_LENGTH_WITH_LANGUAGE_TOKEN - 1 ]
75- not in SUPPORTED_LANGUAGES_TOKENS
76- ):
77- return None
137+ # Validation for appendix
138+ if len (cookie_split ) == MAX_PAYLOAD_LENGTH_WITH_LANGUAGE_TOKEN :
139+ appendix_value = cookie_split [MAX_PAYLOAD_LENGTH_WITH_LANGUAGE_TOKEN - 1 ]
140+ # Backward compatible with legacy appendix
141+ if len (appendix_value ) == APPENDIX_LENGTH_V1 :
142+ if appendix_value not in SUPPORTED_LANGUAGES_TOKENS :
143+ return None
144+ elif len (appendix_value ) != APPENDIX_LENGTH_V2 :
145+ return None
78146
79147 if len (cookie_split ) == MIN_PAYLOAD_SPLIT_LENGTH :
80- updated_cookie = cookie_value + "." + LANGUAGE_TOKEN
148+ updated_cookie = cookie_value + "." + self . appendix_normal
81149 self .cookies_to_set_dict [cookie_name ] = CookieSettings (
82150 cookie_name , updated_cookie , self .etld_plus_one , DEFAULT_1PC_AGE
83151 )
@@ -213,7 +281,7 @@ def _get_updated_fbc_cookie(
213281 + "."
214282 + new_fbc_payload
215283 + "."
216- + LANGUAGE_TOKEN
284+ + self . appendix_new
217285 )
218286 # TODO: update etld+1 to get proper etld+1.
219287 udpated_cookie_setting = CookieSettings (
@@ -240,7 +308,7 @@ def _get_updated_fbp_cookie(
240308 + "."
241309 + new_fbp_payload
242310 + "."
243- + LANGUAGE_TOKEN
311+ + self . appendix_new
244312 )
245313 udpated_cookie_setting = CookieSettings (
246314 FBP_COOKIE_NAME , new_fbp , self .etld_plus_one , DEFAULT_1PC_AGE
0 commit comments