"Fossies" - the Fresh Open Source Software Archive 
Member "selenium-selenium-4.8.1/py/selenium/webdriver/firefox/firefox_profile.py" (17 Feb 2023, 14364 Bytes) of package /linux/www/selenium-selenium-4.8.1.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "firefox_profile.py" see the
Fossies "Dox" file reference documentation and the last
Fossies "Diffs" side-by-side code changes report:
4.7.0_vs_4.8.0.
1 # Licensed to the Software Freedom Conservancy (SFC) under one
2 # or more contributor license agreements. See the NOTICE file
3 # distributed with this work for additional information
4 # regarding copyright ownership. The SFC licenses this file
5 # to you under the Apache License, Version 2.0 (the
6 # "License"); you may not use this file except in compliance
7 # with the License. You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing,
12 # software distributed under the License is distributed on an
13 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 # KIND, either express or implied. See the License for the
15 # specific language governing permissions and limitations
16 # under the License.
17
18 import base64
19 import copy
20 import json
21 import os
22 import re
23 import shutil
24 import sys
25 import tempfile
26 import warnings
27 import zipfile
28 from io import BytesIO
29 from xml.dom import minidom
30
31 from selenium.common.exceptions import WebDriverException
32
33 WEBDRIVER_EXT = "webdriver.xpi"
34 WEBDRIVER_PREFERENCES = "webdriver_prefs.json"
35 EXTENSION_NAME = "fxdriver@googlecode.com"
36
37
38 class AddonFormatError(Exception):
39 """Exception for not well-formed add-on manifest files."""
40
41
42 class FirefoxProfile:
43 ANONYMOUS_PROFILE_NAME = "WEBDRIVER_ANONYMOUS_PROFILE"
44 DEFAULT_PREFERENCES = None
45
46 def __init__(self, profile_directory=None):
47 """Initialises a new instance of a Firefox Profile.
48
49 :args:
50 - profile_directory: Directory of profile that you want to use. If a
51 directory is passed in it will be cloned and the cloned directory
52 will be used by the driver when instantiated.
53 This defaults to None and will create a new
54 directory when object is created.
55 """
56 warnings.warn(
57 "firefox_profile has been deprecated, please use an Options object", DeprecationWarning, stacklevel=2
58 )
59 if not FirefoxProfile.DEFAULT_PREFERENCES:
60 with open(
61 os.path.join(os.path.dirname(__file__), WEBDRIVER_PREFERENCES), encoding="utf-8"
62 ) as default_prefs:
63 FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs)
64
65 self.default_preferences = copy.deepcopy(FirefoxProfile.DEFAULT_PREFERENCES["mutable"])
66 self.profile_dir = profile_directory
67 self.tempfolder = None
68 if not self.profile_dir:
69 self.profile_dir = self._create_tempfolder()
70 else:
71 self.tempfolder = tempfile.mkdtemp()
72 newprof = os.path.join(self.tempfolder, "webdriver-py-profilecopy")
73 shutil.copytree(
74 self.profile_dir, newprof, ignore=shutil.ignore_patterns("parent.lock", "lock", ".parentlock")
75 )
76 self.profile_dir = newprof
77 os.chmod(self.profile_dir, 0o755)
78 self._read_existing_userjs(os.path.join(self.profile_dir, "user.js"))
79 self.extensionsDir = os.path.join(self.profile_dir, "extensions")
80 self.userPrefs = os.path.join(self.profile_dir, "user.js")
81 if os.path.isfile(self.userPrefs):
82 os.chmod(self.userPrefs, 0o644)
83
84 # Public Methods
85 def set_preference(self, key, value):
86 """sets the preference that we want in the profile."""
87 self.default_preferences[key] = value
88
89 def add_extension(self, extension=WEBDRIVER_EXT):
90 self._install_extension(extension)
91
92 def update_preferences(self):
93 for key, value in FirefoxProfile.DEFAULT_PREFERENCES["frozen"].items():
94 # Do not update key that is being set by the user using
95 # set_preference as users are unaware of the freeze properties
96 # and it leads to an inconsistent behavior
97 if key not in self.default_preferences:
98 self.default_preferences[key] = value
99 self._write_user_prefs(self.default_preferences)
100
101 # Properties
102
103 @property
104 def path(self):
105 """Gets the profile directory that is currently being used."""
106 return self.profile_dir
107
108 @property
109 def port(self):
110 """Gets the port that WebDriver is working on."""
111 return self._port
112
113 @port.setter
114 def port(self, port) -> None:
115 """Sets the port that WebDriver will be running on."""
116 if not isinstance(port, int):
117 raise WebDriverException("Port needs to be an integer")
118 try:
119 port = int(port)
120 if port < 1 or port > 65535:
121 raise WebDriverException("Port number must be in the range 1..65535")
122 except (ValueError, TypeError):
123 raise WebDriverException("Port needs to be an integer")
124 self._port = port
125 self.set_preference("webdriver_firefox_port", self._port)
126
127 @property
128 def accept_untrusted_certs(self):
129 return self.default_preferences["webdriver_accept_untrusted_certs"]
130
131 @accept_untrusted_certs.setter
132 def accept_untrusted_certs(self, value) -> None:
133 if value not in [True, False]:
134 raise WebDriverException("Please pass in a Boolean to this call")
135 self.set_preference("webdriver_accept_untrusted_certs", value)
136
137 @property
138 def assume_untrusted_cert_issuer(self):
139 return self.default_preferences["webdriver_assume_untrusted_issuer"]
140
141 @assume_untrusted_cert_issuer.setter
142 def assume_untrusted_cert_issuer(self, value) -> None:
143 if value not in [True, False]:
144 raise WebDriverException("Please pass in a Boolean to this call")
145
146 self.set_preference("webdriver_assume_untrusted_issuer", value)
147
148 @property
149 def encoded(self) -> str:
150 """A zipped, base64 encoded string of profile directory for use with
151 remote WebDriver JSON wire protocol."""
152 self.update_preferences()
153 fp = BytesIO()
154 with zipfile.ZipFile(fp, "w", zipfile.ZIP_DEFLATED) as zipped:
155 path_root = len(self.path) + 1 # account for trailing slash
156 for base, _, files in os.walk(self.path):
157 for fyle in files:
158 filename = os.path.join(base, fyle)
159 zipped.write(filename, filename[path_root:])
160 return base64.b64encode(fp.getvalue()).decode("UTF-8")
161
162 def _create_tempfolder(self):
163 """Creates a temp folder to store User.js and the extension."""
164 return tempfile.mkdtemp()
165
166 def _write_user_prefs(self, user_prefs):
167 """writes the current user prefs dictionary to disk."""
168 with open(self.userPrefs, "w", encoding="utf-8") as f:
169 for key, value in user_prefs.items():
170 f.write(f'user_pref("{key}", {json.dumps(value)});\n')
171
172 def _read_existing_userjs(self, userjs):
173 pref_pattern = re.compile(r'user_pref\("(.*)",\s(.*)\)')
174 try:
175 with open(userjs, encoding="utf-8") as f:
176 for usr in f:
177 matches = pref_pattern.search(usr)
178 try:
179 self.default_preferences[matches.group(1)] = json.loads(matches.group(2))
180 except Exception:
181 warnings.warn(
182 f"(skipping) failed to json.loads existing preference: {matches.group(1) + matches.group(2)}"
183 )
184 except Exception:
185 # The profile given hasn't had any changes made, i.e no users.js
186 pass
187
188 def _install_extension(self, addon, unpack=True):
189 """Installs addon from a filepath, url or directory of addons in the
190 profile.
191
192 - path: url, absolute path to .xpi, or directory of addons
193 - unpack: whether to unpack unless specified otherwise in the install.rdf
194 """
195 if addon == WEBDRIVER_EXT:
196 addon = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT)
197
198 tmpdir = None
199 xpifile = None
200 if addon.endswith(".xpi"):
201 tmpdir = tempfile.mkdtemp(suffix="." + os.path.split(addon)[-1])
202 compressed_file = zipfile.ZipFile(addon, "r")
203 for name in compressed_file.namelist():
204 if name.endswith("/"):
205 if not os.path.isdir(os.path.join(tmpdir, name)):
206 os.makedirs(os.path.join(tmpdir, name))
207 else:
208 if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))):
209 os.makedirs(os.path.dirname(os.path.join(tmpdir, name)))
210 data = compressed_file.read(name)
211 with open(os.path.join(tmpdir, name), "wb") as f:
212 f.write(data)
213 xpifile = addon
214 addon = tmpdir
215
216 # determine the addon id
217 addon_details = self._addon_details(addon)
218 addon_id = addon_details.get("id")
219 assert addon_id, f"The addon id could not be found: {addon}"
220
221 # copy the addon to the profile
222 addon_path = os.path.join(self.extensionsDir, addon_id)
223 if not unpack and not addon_details["unpack"] and xpifile:
224 if not os.path.exists(self.extensionsDir):
225 os.makedirs(self.extensionsDir)
226 os.chmod(self.extensionsDir, 0o755)
227 shutil.copy(xpifile, addon_path + ".xpi")
228 else:
229 if not os.path.exists(addon_path):
230 shutil.copytree(addon, addon_path, symlinks=True)
231
232 # remove the temporary directory, if any
233 if tmpdir:
234 shutil.rmtree(tmpdir)
235
236 def _addon_details(self, addon_path):
237 """Returns a dictionary of details about the addon.
238
239 :param addon_path: path to the add-on directory or XPI
240
241 Returns::
242
243 {'id': u'rainbow@colors.org', # id of the addon
244 'version': u'1.4', # version of the addon
245 'name': u'Rainbow', # name of the addon
246 'unpack': False } # whether to unpack the addon
247 """
248
249 details = {"id": None, "unpack": False, "name": None, "version": None}
250
251 def get_namespace_id(doc, url):
252 attributes = doc.documentElement.attributes
253 namespace = ""
254 for i in range(attributes.length):
255 if attributes.item(i).value == url:
256 if ":" in attributes.item(i).name:
257 # If the namespace is not the default one remove 'xlmns:'
258 namespace = attributes.item(i).name.split(":")[1] + ":"
259 break
260 return namespace
261
262 def get_text(element):
263 """Retrieve the text value of a given node."""
264 rc = []
265 for node in element.childNodes:
266 if node.nodeType == node.TEXT_NODE:
267 rc.append(node.data)
268 return "".join(rc).strip()
269
270 def parse_manifest_json(content):
271 """Extracts the details from the contents of a WebExtensions
272 `manifest.json` file."""
273 manifest = json.loads(content)
274 try:
275 id = manifest["applications"]["gecko"]["id"]
276 except KeyError:
277 id = manifest["name"].replace(" ", "") + "@" + manifest["version"]
278 return {
279 "id": id,
280 "version": manifest["version"],
281 "name": manifest["version"],
282 "unpack": False,
283 }
284
285 if not os.path.exists(addon_path):
286 raise OSError(f"Add-on path does not exist: {addon_path}")
287
288 try:
289 if zipfile.is_zipfile(addon_path):
290 # Bug 944361 - We cannot use 'with' together with zipFile because
291 # it will cause an exception thrown in Python 2.6.
292 # TODO: use with statement when Python 2.x is no longer supported
293 try:
294 compressed_file = zipfile.ZipFile(addon_path, "r")
295 if "manifest.json" in compressed_file.namelist():
296 return parse_manifest_json(compressed_file.read("manifest.json"))
297
298 manifest = compressed_file.read("install.rdf")
299 finally:
300 compressed_file.close()
301 elif os.path.isdir(addon_path):
302 manifest_json_filename = os.path.join(addon_path, "manifest.json")
303 if os.path.exists(manifest_json_filename):
304 with open(manifest_json_filename, encoding="utf-8") as f:
305 return parse_manifest_json(f.read())
306
307 with open(os.path.join(addon_path, "install.rdf"), encoding="utf-8") as f:
308 manifest = f.read()
309 else:
310 raise OSError(f"Add-on path is neither an XPI nor a directory: {addon_path}")
311 except (OSError, KeyError) as e:
312 raise AddonFormatError(str(e), sys.exc_info()[2])
313
314 try:
315 doc = minidom.parseString(manifest)
316
317 # Get the namespaces abbreviations
318 em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#")
319 rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
320
321 description = doc.getElementsByTagName(rdf + "Description").item(0)
322 if not description:
323 description = doc.getElementsByTagName("Description").item(0)
324 for node in description.childNodes:
325 # Remove the namespace prefix from the tag for comparison
326 entry = node.nodeName.replace(em, "")
327 if entry in details:
328 details.update({entry: get_text(node)})
329 if not details.get("id"):
330 for i in range(description.attributes.length):
331 attribute = description.attributes.item(i)
332 if attribute.name == em + "id":
333 details.update({"id": attribute.value})
334 except Exception as e:
335 raise AddonFormatError(str(e), sys.exc_info()[2])
336
337 # turn unpack into a true/false value
338 if isinstance(details["unpack"], str):
339 details["unpack"] = details["unpack"].lower() == "true"
340
341 # If no ID is set, the add-on is invalid
342 if not details.get("id"):
343 raise AddonFormatError("Add-on id could not be found.")
344
345 return details