"Fossies" - the Fresh Open Source Software Archive

Member "selenium-selenium-4.8.1/py/selenium/webdriver/remote/remote_connection.py" (17 Feb 2023, 16829 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 "remote_connection.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 4.8.0_vs_4.8.1.

    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 logging
   19 import os
   20 import platform
   21 import socket
   22 import string
   23 from base64 import b64encode
   24 from urllib import parse
   25 
   26 import certifi
   27 import urllib3
   28 
   29 from selenium import __version__
   30 
   31 from . import utils
   32 from .command import Command
   33 from .errorhandler import ErrorCode
   34 
   35 LOGGER = logging.getLogger(__name__)
   36 
   37 
   38 class RemoteConnection:
   39     """A connection with the Remote WebDriver server.
   40 
   41     Communicates with the server using the WebDriver wire protocol:
   42     https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
   43     """
   44 
   45     browser_name = None
   46     _timeout = socket._GLOBAL_DEFAULT_TIMEOUT
   47     _ca_certs = certifi.where()
   48 
   49     @classmethod
   50     def get_timeout(cls):
   51         """
   52         :Returns:
   53             Timeout value in seconds for all http requests made to the Remote Connection
   54         """
   55         return None if cls._timeout == socket._GLOBAL_DEFAULT_TIMEOUT else cls._timeout
   56 
   57     @classmethod
   58     def set_timeout(cls, timeout):
   59         """Override the default timeout.
   60 
   61         :Args:
   62             - timeout - timeout value for http requests in seconds
   63         """
   64         cls._timeout = timeout
   65 
   66     @classmethod
   67     def reset_timeout(cls):
   68         """Reset the http request timeout to socket._GLOBAL_DEFAULT_TIMEOUT."""
   69         cls._timeout = socket._GLOBAL_DEFAULT_TIMEOUT
   70 
   71     @classmethod
   72     def get_certificate_bundle_path(cls):
   73         """
   74         :Returns:
   75             Paths of the .pem encoded certificate to verify connection to command executor
   76         """
   77         return cls._ca_certs
   78 
   79     @classmethod
   80     def set_certificate_bundle_path(cls, path):
   81         """Set the path to the certificate bundle to verify connection to
   82         command executor. Can also be set to None to disable certificate
   83         validation.
   84 
   85         :Args:
   86             - path - path of a .pem encoded certificate chain.
   87         """
   88         cls._ca_certs = path
   89 
   90     @classmethod
   91     def get_remote_connection_headers(cls, parsed_url, keep_alive=False):
   92         """Get headers for remote request.
   93 
   94         :Args:
   95          - parsed_url - The parsed url
   96          - keep_alive (Boolean) - Is this a keep-alive connection (default: False)
   97         """
   98 
   99         system = platform.system().lower()
  100         if system == "darwin":
  101             system = "mac"
  102 
  103         headers = {
  104             "Accept": "application/json",
  105             "Content-Type": "application/json;charset=UTF-8",
  106             "User-Agent": f"selenium/{__version__} (python {system})",
  107         }
  108 
  109         if parsed_url.username:
  110             base64string = b64encode(f"{parsed_url.username}:{parsed_url.password}".encode())
  111             headers.update({"Authorization": f"Basic {base64string.decode()}"})
  112 
  113         if keep_alive:
  114             headers.update({"Connection": "keep-alive"})
  115 
  116         return headers
  117 
  118     def _get_proxy_url(self):
  119         if self._url.startswith("https://"):
  120             return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY"))
  121         if self._url.startswith("http://"):
  122             return os.environ.get("http_proxy", os.environ.get("HTTP_PROXY"))
  123 
  124     def _identify_http_proxy_auth(self):
  125         url = self._proxy_url
  126         url = url[url.find(":") + 3 :]
  127         return "@" in url and len(url[: url.find("@")]) > 0
  128 
  129     def _separate_http_proxy_auth(self):
  130         url = self._proxy_url
  131         protocol = url[: url.find(":") + 3]
  132         no_protocol = url[len(protocol) :]
  133         auth = no_protocol[: no_protocol.find("@")]
  134         proxy_without_auth = protocol + no_protocol[len(auth) + 1 :]
  135         return proxy_without_auth, auth
  136 
  137     def _get_connection_manager(self):
  138         pool_manager_init_args = {"timeout": self.get_timeout()}
  139         if self._ca_certs:
  140             pool_manager_init_args["cert_reqs"] = "CERT_REQUIRED"
  141             pool_manager_init_args["ca_certs"] = self._ca_certs
  142 
  143         if self._proxy_url:
  144             if self._proxy_url.lower().startswith("sock"):
  145                 from urllib3.contrib.socks import SOCKSProxyManager
  146 
  147                 return SOCKSProxyManager(self._proxy_url, **pool_manager_init_args)
  148             if self._identify_http_proxy_auth():
  149                 self._proxy_url, self._basic_proxy_auth = self._separate_http_proxy_auth()
  150                 pool_manager_init_args["proxy_headers"] = urllib3.make_headers(proxy_basic_auth=self._basic_proxy_auth)
  151             return urllib3.ProxyManager(self._proxy_url, **pool_manager_init_args)
  152 
  153         return urllib3.PoolManager(**pool_manager_init_args)
  154 
  155     def __init__(self, remote_server_addr: str, keep_alive: bool = False, ignore_proxy: bool = False):
  156         self.keep_alive = keep_alive
  157         self._url = remote_server_addr
  158 
  159         # Env var NO_PROXY will override this part of the code
  160         _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY"))
  161         if _no_proxy:
  162             for npu in _no_proxy.split(","):
  163                 npu = npu.strip()
  164                 if npu == "*":
  165                     ignore_proxy = True
  166                     break
  167                 n_url = parse.urlparse(npu)
  168                 remote_add = parse.urlparse(self._url)
  169                 if n_url.netloc:
  170                     if remote_add.netloc == n_url.netloc:
  171                         ignore_proxy = True
  172                         break
  173                 else:
  174                     if n_url.path in remote_add.netloc:
  175                         ignore_proxy = True
  176                         break
  177 
  178         self._proxy_url = self._get_proxy_url() if not ignore_proxy else None
  179         if keep_alive:
  180             self._conn = self._get_connection_manager()
  181 
  182         self._commands = {
  183             Command.NEW_SESSION: ("POST", "/session"),
  184             Command.QUIT: ("DELETE", "/session/$sessionId"),
  185             Command.W3C_GET_CURRENT_WINDOW_HANDLE: ("GET", "/session/$sessionId/window"),
  186             Command.W3C_GET_WINDOW_HANDLES: ("GET", "/session/$sessionId/window/handles"),
  187             Command.GET: ("POST", "/session/$sessionId/url"),
  188             Command.GO_FORWARD: ("POST", "/session/$sessionId/forward"),
  189             Command.GO_BACK: ("POST", "/session/$sessionId/back"),
  190             Command.REFRESH: ("POST", "/session/$sessionId/refresh"),
  191             Command.W3C_EXECUTE_SCRIPT: ("POST", "/session/$sessionId/execute/sync"),
  192             Command.W3C_EXECUTE_SCRIPT_ASYNC: ("POST", "/session/$sessionId/execute/async"),
  193             Command.GET_CURRENT_URL: ("GET", "/session/$sessionId/url"),
  194             Command.GET_TITLE: ("GET", "/session/$sessionId/title"),
  195             Command.GET_PAGE_SOURCE: ("GET", "/session/$sessionId/source"),
  196             Command.SCREENSHOT: ("GET", "/session/$sessionId/screenshot"),
  197             Command.ELEMENT_SCREENSHOT: ("GET", "/session/$sessionId/element/$id/screenshot"),
  198             Command.FIND_ELEMENT: ("POST", "/session/$sessionId/element"),
  199             Command.FIND_ELEMENTS: ("POST", "/session/$sessionId/elements"),
  200             Command.W3C_GET_ACTIVE_ELEMENT: ("GET", "/session/$sessionId/element/active"),
  201             Command.FIND_CHILD_ELEMENT: ("POST", "/session/$sessionId/element/$id/element"),
  202             Command.FIND_CHILD_ELEMENTS: ("POST", "/session/$sessionId/element/$id/elements"),
  203             Command.CLICK_ELEMENT: ("POST", "/session/$sessionId/element/$id/click"),
  204             Command.CLEAR_ELEMENT: ("POST", "/session/$sessionId/element/$id/clear"),
  205             Command.GET_ELEMENT_TEXT: ("GET", "/session/$sessionId/element/$id/text"),
  206             Command.SEND_KEYS_TO_ELEMENT: ("POST", "/session/$sessionId/element/$id/value"),
  207             Command.UPLOAD_FILE: ("POST", "/session/$sessionId/se/file"),
  208             Command.GET_ELEMENT_TAG_NAME: ("GET", "/session/$sessionId/element/$id/name"),
  209             Command.IS_ELEMENT_SELECTED: ("GET", "/session/$sessionId/element/$id/selected"),
  210             Command.IS_ELEMENT_ENABLED: ("GET", "/session/$sessionId/element/$id/enabled"),
  211             Command.GET_ELEMENT_RECT: ("GET", "/session/$sessionId/element/$id/rect"),
  212             Command.GET_ELEMENT_ATTRIBUTE: ("GET", "/session/$sessionId/element/$id/attribute/$name"),
  213             Command.GET_ELEMENT_PROPERTY: ("GET", "/session/$sessionId/element/$id/property/$name"),
  214             Command.GET_ELEMENT_ARIA_ROLE: ("GET", "/session/$sessionId/element/$id/computedrole"),
  215             Command.GET_ELEMENT_ARIA_LABEL: ("GET", "/session/$sessionId/element/$id/computedlabel"),
  216             Command.GET_SHADOW_ROOT: ("GET", "/session/$sessionId/element/$id/shadow"),
  217             Command.FIND_ELEMENT_FROM_SHADOW_ROOT: ("POST", "/session/$sessionId/shadow/$shadowId/element"),
  218             Command.FIND_ELEMENTS_FROM_SHADOW_ROOT: ("POST", "/session/$sessionId/shadow/$shadowId/elements"),
  219             Command.GET_ALL_COOKIES: ("GET", "/session/$sessionId/cookie"),
  220             Command.ADD_COOKIE: ("POST", "/session/$sessionId/cookie"),
  221             Command.GET_COOKIE: ("GET", "/session/$sessionId/cookie/$name"),
  222             Command.DELETE_ALL_COOKIES: ("DELETE", "/session/$sessionId/cookie"),
  223             Command.DELETE_COOKIE: ("DELETE", "/session/$sessionId/cookie/$name"),
  224             Command.SWITCH_TO_FRAME: ("POST", "/session/$sessionId/frame"),
  225             Command.SWITCH_TO_PARENT_FRAME: ("POST", "/session/$sessionId/frame/parent"),
  226             Command.SWITCH_TO_WINDOW: ("POST", "/session/$sessionId/window"),
  227             Command.NEW_WINDOW: ("POST", "/session/$sessionId/window/new"),
  228             Command.CLOSE: ("DELETE", "/session/$sessionId/window"),
  229             Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY: ("GET", "/session/$sessionId/element/$id/css/$propertyName"),
  230             Command.EXECUTE_ASYNC_SCRIPT: ("POST", "/session/$sessionId/execute_async"),
  231             Command.SET_TIMEOUTS: ("POST", "/session/$sessionId/timeouts"),
  232             Command.GET_TIMEOUTS: ("GET", "/session/$sessionId/timeouts"),
  233             Command.W3C_DISMISS_ALERT: ("POST", "/session/$sessionId/alert/dismiss"),
  234             Command.W3C_ACCEPT_ALERT: ("POST", "/session/$sessionId/alert/accept"),
  235             Command.W3C_SET_ALERT_VALUE: ("POST", "/session/$sessionId/alert/text"),
  236             Command.W3C_GET_ALERT_TEXT: ("GET", "/session/$sessionId/alert/text"),
  237             Command.W3C_ACTIONS: ("POST", "/session/$sessionId/actions"),
  238             Command.W3C_CLEAR_ACTIONS: ("DELETE", "/session/$sessionId/actions"),
  239             Command.SET_WINDOW_RECT: ("POST", "/session/$sessionId/window/rect"),
  240             Command.GET_WINDOW_RECT: ("GET", "/session/$sessionId/window/rect"),
  241             Command.W3C_MAXIMIZE_WINDOW: ("POST", "/session/$sessionId/window/maximize"),
  242             Command.SET_SCREEN_ORIENTATION: ("POST", "/session/$sessionId/orientation"),
  243             Command.GET_SCREEN_ORIENTATION: ("GET", "/session/$sessionId/orientation"),
  244             Command.GET_NETWORK_CONNECTION: ("GET", "/session/$sessionId/network_connection"),
  245             Command.SET_NETWORK_CONNECTION: ("POST", "/session/$sessionId/network_connection"),
  246             Command.GET_LOG: ("POST", "/session/$sessionId/se/log"),
  247             Command.GET_AVAILABLE_LOG_TYPES: ("GET", "/session/$sessionId/se/log/types"),
  248             Command.CURRENT_CONTEXT_HANDLE: ("GET", "/session/$sessionId/context"),
  249             Command.CONTEXT_HANDLES: ("GET", "/session/$sessionId/contexts"),
  250             Command.SWITCH_TO_CONTEXT: ("POST", "/session/$sessionId/context"),
  251             Command.FULLSCREEN_WINDOW: ("POST", "/session/$sessionId/window/fullscreen"),
  252             Command.MINIMIZE_WINDOW: ("POST", "/session/$sessionId/window/minimize"),
  253             Command.PRINT_PAGE: ("POST", "/session/$sessionId/print"),
  254             Command.ADD_VIRTUAL_AUTHENTICATOR: ("POST", "/session/$sessionId/webauthn/authenticator"),
  255             Command.REMOVE_VIRTUAL_AUTHENTICATOR: (
  256                 "DELETE",
  257                 "/session/$sessionId/webauthn/authenticator/$authenticatorId",
  258             ),
  259             Command.ADD_CREDENTIAL: ("POST", "/session/$sessionId/webauthn/authenticator/$authenticatorId/credential"),
  260             Command.GET_CREDENTIALS: ("GET", "/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials"),
  261             Command.REMOVE_CREDENTIAL: (
  262                 "DELETE",
  263                 "/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials/$credentialId",
  264             ),
  265             Command.REMOVE_ALL_CREDENTIALS: (
  266                 "DELETE",
  267                 "/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials",
  268             ),
  269             Command.SET_USER_VERIFIED: ("POST", "/session/$sessionId/webauthn/authenticator/$authenticatorId/uv"),
  270         }
  271 
  272     def execute(self, command, params):
  273         """Send a command to the remote server.
  274 
  275         Any path substitutions required for the URL mapped to the command should be
  276         included in the command parameters.
  277 
  278         :Args:
  279          - command - A string specifying the command to execute.
  280          - params - A dictionary of named parameters to send with the command as
  281            its JSON payload.
  282         """
  283         command_info = self._commands[command]
  284         assert command_info is not None, f"Unrecognised command {command}"
  285         path = string.Template(command_info[1]).substitute(params)
  286         if isinstance(params, dict) and "sessionId" in params:
  287             del params["sessionId"]
  288         data = utils.dump_json(params)
  289         url = f"{self._url}{path}"
  290         return self._request(command_info[0], url, body=data)
  291 
  292     def _request(self, method, url, body=None):
  293         """Send an HTTP request to the remote server.
  294 
  295         :Args:
  296          - method - A string for the HTTP method to send the request with.
  297          - url - A string for the URL to send the request to.
  298          - body - A string for request body. Ignored unless method is POST or PUT.
  299 
  300         :Returns:
  301           A dictionary with the server's parsed JSON response.
  302         """
  303         LOGGER.debug(f"{method} {url} {body}")
  304         parsed_url = parse.urlparse(url)
  305         headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
  306         response = None
  307         if body and method not in ("POST", "PUT"):
  308             body = None
  309 
  310         if self.keep_alive:
  311             response = self._conn.request(method, url, body=body, headers=headers)
  312             statuscode = response.status
  313         else:
  314             conn = self._get_connection_manager()
  315             with conn as http:
  316                 response = http.request(method, url, body=body, headers=headers)
  317             statuscode = response.status
  318         data = response.data.decode("UTF-8")
  319         LOGGER.debug(f"Remote response: status={response.status} | data={data} | headers={response.headers}")
  320         try:
  321             if 300 <= statuscode < 304:
  322                 return self._request("GET", response.headers.get("location", None))
  323             if 399 < statuscode <= 500:
  324                 return {"status": statuscode, "value": data}
  325             content_type = []
  326             if response.headers.get("Content-Type", None):
  327                 content_type = response.headers.get("Content-Type", None).split(";")
  328             if not any([x.startswith("image/png") for x in content_type]):
  329                 try:
  330                     data = utils.load_json(data.strip())
  331                 except ValueError:
  332                     if 199 < statuscode < 300:
  333                         status = ErrorCode.SUCCESS
  334                     else:
  335                         status = ErrorCode.UNKNOWN_ERROR
  336                     return {"status": status, "value": data.strip()}
  337 
  338                 # Some drivers incorrectly return a response
  339                 # with no 'value' field when they should return null.
  340                 if "value" not in data:
  341                     data["value"] = None
  342                 return data
  343             data = {"status": 0, "value": data}
  344             return data
  345         finally:
  346             LOGGER.debug("Finished Request")
  347             response.close()
  348 
  349     def close(self):
  350         """Clean up resources when finished with the remote_connection."""
  351         if hasattr(self, "_conn"):
  352             self._conn.clear()