def _verify_message( self, msg: QDBusMessage, expected_signature: str, expected_type: QDBusMessage.MessageType, ) -> None: """Check the signature/type of a received message. Raises DBusError if the signature doesn't match. """ assert expected_type not in [ QDBusMessage.ErrorMessage, QDBusMessage.InvalidMessage, ], expected_type if msg.type() == QDBusMessage.ErrorMessage: raise DBusError(msg) signature = msg.signature() if signature != expected_signature: raise Error( f"Got a message with signature {signature} but expected " f"{expected_signature} (args: {msg.arguments()})") typ = msg.type() if typ != expected_type: type_str = debug.qenum_key(QDBusMessage.MessageType, typ) expected_type_str = debug.qenum_key(QDBusMessage.MessageType, expected_type) raise Error( f"Got a message of type {type_str} but expected {expected_type_str}" f"(args: {msg.arguments()})")
def interceptRequest(self, info): """Handle the given request. Reimplementing this virtual function and setting the interceptor on a profile makes it possible to intercept URL requests. This function is executed on the IO thread, and therefore running long tasks here will block networking. info contains the information about the URL request and will track internally whether its members have been altered. Args: info: QWebEngineUrlRequestInfo &info """ if 'log-requests' in self._args.debug_flags: resource_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.resourceType()) navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.navigationType()) log.webview.debug("{} {}, first-party {}, resource {}, " "navigation {}".format( bytes(info.requestMethod()).decode('ascii'), info.requestUrl().toDisplayString(), info.firstPartyUrl().toDisplayString(), resource_type, navigation_type)) url = info.requestUrl() first_party = info.firstPartyUrl() if ((url.scheme(), url.host(), url.path()) == ('qute', 'settings', '/set')): if (first_party != QUrl('qute://settings/') or info.resourceType() != QWebEngineUrlRequestInfo.ResourceTypeXhr): log.webview.warning("Blocking malicious request from {} to {}" .format(first_party.toDisplayString(), url.toDisplayString())) info.block(True) return # FIXME:qtwebengine only block ads for NavigationTypeOther? request = interceptors.Request(first_party_url=first_party, request_url=url) interceptors.run(request) if request.is_blocked: info.block(True) for header, value in shared.custom_headers(url=url): info.setHttpHeader(header, value) user_agent = config.instance.get('content.headers.user_agent', url=url) if user_agent is not None: info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
def interceptRequest(self, info): """Handle the given request. Reimplementing this virtual function and setting the interceptor on a profile makes it possible to intercept URL requests. This function is executed on the IO thread, and therefore running long tasks here will block networking. info contains the information about the URL request and will track internally whether its members have been altered. Args: info: QWebEngineUrlRequestInfo &info """ if 'log-requests' in self._args.debug_flags: resource_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.resourceType()) navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.navigationType()) log.webview.debug("{} {}, first-party {}, resource {}, " "navigation {}".format( bytes(info.requestMethod()).decode('ascii'), info.requestUrl().toDisplayString(), info.firstPartyUrl().toDisplayString(), resource_type, navigation_type)) url = info.requestUrl() first_party = info.firstPartyUrl() if ((url.scheme(), url.host(), url.path()) == ('qute', 'settings', '/set')): if (first_party != QUrl('qute://settings/') or info.resourceType() != QWebEngineUrlRequestInfo.ResourceTypeXhr): log.webview.warning( "Blocking malicious request from {} to {}".format( first_party.toDisplayString(), url.toDisplayString())) info.block(True) return # FIXME:qtwebengine only block ads for NavigationTypeOther? request = interceptors.Request(first_party_url=first_party, request_url=url) interceptors.run(request) if request.is_blocked: info.block(True) for header, value in shared.custom_headers(url=url): info.setHttpHeader(header, value) user_agent = config.instance.get('content.headers.user_agent', url=url) if user_agent is not None: info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
def _log_error(error): """Log informations about a SQL error to the debug log.""" log.sql.debug("SQL error:") log.sql.debug("type: {}".format(debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(error.databaseText())) log.sql.debug("driver text: {}".format(error.driverText())) log.sql.debug("error code: {}".format(error.nativeErrorCode()))
def raise_sqlite_error(msg, error): """Raise either a SqlBugError or SqlEnvironmentError.""" error_code = error.nativeErrorCode() database_text = error.databaseText() driver_text = error.driverText() log.sql.debug("SQL error:") log.sql.debug("type: {}".format( debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(database_text)) log.sql.debug("driver text: {}".format(driver_text)) log.sql.debug("error code: {}".format(error_code)) environmental_errors = [ SqliteErrorCode.BUSY, SqliteErrorCode.READONLY, SqliteErrorCode.IOERR, SqliteErrorCode.CORRUPT, SqliteErrorCode.FULL, SqliteErrorCode.CANTOPEN, ] # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70506 # We don't know what the actual error was, but let's assume it's not us to # blame... Usually this is something like an unreadable database file. qtbug_70506 = (error_code == SqliteErrorCode.UNKNOWN and driver_text == "Error opening database" and database_text == "out of memory") if error_code in environmental_errors or qtbug_70506: raise SqlEnvironmentError(msg, error) else: raise SqlBugError(msg, error)
def __init__(self, msg, error): super().__init__(msg) self.error = error log.sql.debug("SQL error:") log.sql.debug("type: {}".format( debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(error.databaseText())) log.sql.debug("driver text: {}".format(error.driverText())) log.sql.debug("error code: {}".format(error.nativeErrorCode())) # https://sqlite.org/rescode.html # https://github.com/qutebrowser/qutebrowser/issues/2930 # https://github.com/qutebrowser/qutebrowser/issues/3004 environmental_errors = [ '5', # SQLITE_BUSY ("database is locked") '8', # SQLITE_READONLY '11', # SQLITE_CORRUPT '13', # SQLITE_FULL ] # At least in init(), we can get errors like this: # type: ConnectionError # database text: out of memory # driver text: Error opening database # error code: -1 environmental_strings = [ "out of memory", ] errcode = error.nativeErrorCode() self.environmental = ( errcode in environmental_errors or (errcode == -1 and error.databaseText() in environmental_strings))
def acceptNavigationRequest(self, url: QUrl, typ: QWebEnginePage.NavigationType, is_main_frame: bool): """Override acceptNavigationRequest to handle clicked links. Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound to linkClicked won't work correctly, because when in a frameset, we have no idea in which frame the link should be opened. Checks if it should open it in a tab (middle-click or control) or not, and then conditionally opens the URL. Opening it in a new tab/window is handled in the slot connected to link_clicked. """ target = self._tabdata.combined_target() log.webview.debug("navigation request: url {}, type {}, " "target {}, is_main_frame {}".format( url.toDisplayString(), debug.qenum_key(QWebEnginePage, typ), target, is_main_frame)) if typ != QWebEnginePage.NavigationTypeLinkClicked: return True self.link_clicked.emit(url) return url.isValid() and target == usertypes.ClickTarget.normal
def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. This function is called from the createWindow() method of the associated QWebPage, each time the page wants to create a new window of the given type. This might be the result, for example, of a JavaScript request to open a document in a new window. Args: wintype: This enum describes the types of window that can be created by the createWindow() function. QWebPage::WebBrowserWindow: The window is a regular web browser window. QWebPage::WebModalDialog: The window acts as modal dialog. Return: The new QWebView object. """ debug_type = debug.qenum_key(QWebPage, wintype) log.webview.debug("createWindow with type {}".format(debug_type)) if wintype == QWebPage.WebModalDialog: log.webview.warning("WebModalDialog requested, but we don't " "support that!") tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self.win_id) # pylint: disable=protected-access return tabbed_browser.tabopen(background=False)._widget
def _on_state_changed(self, state): state_name = debug.qenum_key(QWebEngineDownloadItem, state) log.downloads.debug("State for {!r} changed to {}".format( self, state_name)) if state == QWebEngineDownloadItem.DownloadRequested: pass elif state == QWebEngineDownloadItem.DownloadInProgress: pass elif state == QWebEngineDownloadItem.DownloadCompleted: log.downloads.debug("Download {} finished".format(self.basename)) self.successful = True self.done = True self.finished.emit() self.stats.finish() elif state == QWebEngineDownloadItem.DownloadCancelled: self.successful = False self.done = True self.cancelled.emit() self.stats.finish() elif state == QWebEngineDownloadItem.DownloadInterrupted: self.successful = False self.done = True # https://bugreports.qt.io/browse/QTBUG-56839 self.error.emit("Download failed") self.stats.finish() else: raise ValueError("_on_state_changed was called with unknown state " "{}".format(state_name))
def _writable_location(typ): """Wrapper around QStandardPaths.writableLocation. Arguments: typ: A QStandardPaths::StandardLocation member. """ typ_str = debug.qenum_key(QStandardPaths, typ) # Types we are sure we handle correctly below. assert typ in [ QStandardPaths.ConfigLocation, QStandardPaths.DataLocation, QStandardPaths.CacheLocation, QStandardPaths.DownloadLocation, QStandardPaths.RuntimeLocation, QStandardPaths.TempLocation, # FIXME old Qt getattr(QStandardPaths, 'AppDataLocation', object())], typ_str with _unset_organization(): path = QStandardPaths.writableLocation(typ) log.misc.debug("writable location for {}: {}".format(typ_str, path)) if not path: raise EmptyValueError("QStandardPaths returned an empty value!") # Qt seems to use '/' as path separator even on Windows... path = path.replace('/', os.sep) # Add the application name to the given path if needed. # This is in order for this to work without a QApplication (and thus # QStandardsPaths not knowing the application name), as well as a # workaround for https://bugreports.qt.io/browse/QTBUG-38872 if (typ != QStandardPaths.DownloadLocation and path.split(os.sep)[-1] != APPNAME): path = os.path.join(path, APPNAME) return path
def createRequest(self, op, req, outgoing_data): """Return a new QNetworkReply object. Args: op: Operation op req: const QNetworkRequest & req outgoing_data: QIODevice * outgoingData Return: A QNetworkReply. """ if proxymod.application_factory is not None: proxy_error = proxymod.application_factory.get_error() if proxy_error is not None: return networkreply.ErrorNetworkReply( req, proxy_error, QNetworkReply.UnknownProxyError, self) if not req.url().isValid(): log.network.debug("Ignoring invalid requested URL: {}".format( req.url().errorString())) return networkreply.ErrorNetworkReply( req, "Invalid request URL", QNetworkReply.HostNotFoundError, self) for header, value in shared.custom_headers(url=req.url()): req.setRawHeader(header, value) tab = self._get_tab() current_url = QUrl() if tab is not None: try: current_url = tab.url() except RuntimeError: # We could be in the middle of the webpage shutdown here. pass request = interceptors.Request(first_party_url=current_url, request_url=req.url()) interceptors.run(request) if request.is_blocked: return networkreply.ErrorNetworkReply( req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied, self) if 'log-requests' in objects.debug_flags: operation = debug.qenum_key(QNetworkAccessManager, op) operation = operation.replace('Operation', '').upper() log.network.debug("{} {}, first-party {}".format( operation, req.url().toDisplayString(), current_url.toDisplayString())) scheme = req.url().scheme() if scheme in self._scheme_handlers: result = self._scheme_handlers[scheme](req, op, current_url) if result is not None: result.setParent(self) return result self.set_referer(req, current_url) return super().createRequest(op, req, outgoing_data)
def _on_state_changed(self, state): state_name = debug.qenum_key(QWebEngineDownloadItem, state) log.downloads.debug("State for {!r} changed to {}".format( self, state_name)) if state == QWebEngineDownloadItem.DownloadRequested: pass elif state == QWebEngineDownloadItem.DownloadInProgress: pass elif state == QWebEngineDownloadItem.DownloadCompleted: log.downloads.debug("Download {} finished".format(self.basename)) if self._is_page_download(): # Same logging as QtWebKit mhtml downloads. log.downloads.debug("File successfully written.") self.successful = True self.done = True self.finished.emit() self.stats.finish() elif state == QWebEngineDownloadItem.DownloadCancelled: self.successful = False self.done = True self.cancelled.emit() self.stats.finish() elif state == QWebEngineDownloadItem.DownloadInterrupted: self.successful = False # https://bugreports.qt.io/browse/QTBUG-56839 try: reason = self._qt_item.interruptReasonString() except AttributeError: # Qt < 5.9 reason = "Download failed" self._die(reason) else: raise ValueError("_on_state_changed was called with unknown state " "{}".format(state_name))
def _ensure_can_set_filename(self, filename): state = self._qt_item.state() if state != QWebEngineDownloadItem.DownloadRequested: state_name = debug.qenum_key(QWebEngineDownloadItem, state) raise ValueError("Trying to set filename {} on {!r} which is " "state {} (not in requested state)!".format( filename, self, state_name))
def __repr__(self) -> str: return utils.get_repr(self, errors=[ debug.qenum_key(QSslError, err.error()) for err in self._errors ], string=str(self))
def raise_sqlite_error(msg, error): """Raise either a BugError or KnownError.""" error_code = error.nativeErrorCode() database_text = error.databaseText() driver_text = error.driverText() log.sql.debug("SQL error:") log.sql.debug("type: {}".format(debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(database_text)) log.sql.debug("driver text: {}".format(driver_text)) log.sql.debug("error code: {}".format(error_code)) known_errors = [ SqliteErrorCode.BUSY, SqliteErrorCode.READONLY, SqliteErrorCode.IOERR, SqliteErrorCode.CORRUPT, SqliteErrorCode.FULL, SqliteErrorCode.CANTOPEN, SqliteErrorCode.PROTOCOL, SqliteErrorCode.NOTADB, ] # https://github.com/qutebrowser/qutebrowser/issues/4681 # If the query we built was too long too_long_err = ( error_code == SqliteErrorCode.ERROR and (database_text.startswith("Expression tree is too large") or database_text in ["too many SQL variables", "LIKE or GLOB pattern too complex"])) if error_code in known_errors or too_long_err: raise KnownError(msg, error) raise BugError(msg, error)
def raise_sqlite_error(msg, error): """Raise either a SqlBugError or SqlEnvironmentError.""" log.sql.debug("SQL error:") log.sql.debug("type: {}".format( debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(error.databaseText())) log.sql.debug("driver text: {}".format(error.driverText())) log.sql.debug("error code: {}".format(error.nativeErrorCode())) environmental_errors = [ SqliteErrorCode.BUSY, SqliteErrorCode.READONLY, SqliteErrorCode.IOERR, SqliteErrorCode.CORRUPT, SqliteErrorCode.FULL, SqliteErrorCode.CANTOPEN, ] # At least in init(), we can get errors like this: # > type: ConnectionError # > database text: out of memory # > driver text: Error opening database # > error code: -1 environmental_strings = [ "out of memory", ] errcode = error.nativeErrorCode() if (errcode in environmental_errors or (errcode == -1 and error.databaseText() in environmental_strings)): raise SqlEnvironmentError(msg, error) else: raise SqlBugError(msg, error)
def _writable_location(typ: QStandardPaths.StandardLocation) -> str: """Wrapper around QStandardPaths.writableLocation. Arguments: typ: A QStandardPaths::StandardLocation member. """ typ_str = debug.qenum_key(QStandardPaths, typ) # Types we are sure we handle correctly below. assert typ in [ QStandardPaths.ConfigLocation, QStandardPaths.AppLocalDataLocation, QStandardPaths.CacheLocation, QStandardPaths.DownloadLocation, QStandardPaths.RuntimeLocation, QStandardPaths.TempLocation, QStandardPaths.AppDataLocation ], typ_str with _unset_organization(): path = QStandardPaths.writableLocation(typ) log.misc.debug("writable location for {}: {}".format(typ_str, path)) if not path: raise EmptyValueError("QStandardPaths returned an empty value!") # Qt seems to use '/' as path separator even on Windows... path = path.replace('/', os.sep) # Add the application name to the given path if needed. # This is in order for this to work without a QApplication (and thus # QStandardsPaths not knowing the application name). if (typ != QStandardPaths.DownloadLocation and path.split(os.sep)[-1] != APPNAME): path = os.path.join(path, APPNAME) return path
def _do_cancel(self): state = self._qt_item.state() state_name = debug.qenum_key(QWebEngineDownloadItem, state) assert state not in [ QWebEngineDownloadItem.DownloadCompleted, QWebEngineDownloadItem.DownloadCancelled ], state_name self._qt_item.cancel()
def test_no_metaobj(self): """Test with an enum with no metaobject.""" with self.assertRaises(AttributeError): # Make sure it doesn't have a meta object # pylint: disable=pointless-statement,no-member QStyle.PrimitiveElement.staticMetaObject key = debug.qenum_key(QStyle, QStyle.PE_PanelButtonCommand) self.assertEqual(key, 'PE_PanelButtonCommand')
def _on_feature_permission_requested(self, url, feature): """Ask the user for approval for geolocation/media/etc..""" options = { QWebEnginePage.Geolocation: 'content.geolocation', QWebEnginePage.MediaAudioCapture: 'content.media_capture', QWebEnginePage.MediaVideoCapture: 'content.media_capture', QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture', } messages = { QWebEnginePage.Geolocation: 'access your location', QWebEnginePage.MediaAudioCapture: 'record audio', QWebEnginePage.MediaVideoCapture: 'record video', QWebEnginePage.MediaAudioVideoCapture: 'record audio/video', } try: options.update({ QWebEnginePage.DesktopVideoCapture: 'content.desktop_capture', QWebEnginePage.DesktopAudioVideoCapture: 'content.desktop_capture', }) messages.update({ QWebEnginePage.DesktopVideoCapture: 'capture your desktop', QWebEnginePage.DesktopAudioVideoCapture: 'capture your desktop and audio', }) except AttributeError: # Added in Qt 5.10 pass assert options.keys() == messages.keys() page = self._widget.page() if feature not in options: log.webview.error("Unhandled feature permission {}".format( debug.qenum_key(QWebEnginePage, feature))) page.setFeaturePermission(url, feature, QWebEnginePage.PermissionDeniedByUser) return yes_action = functools.partial(page.setFeaturePermission, url, feature, QWebEnginePage.PermissionGrantedByUser) no_action = functools.partial(page.setFeaturePermission, url, feature, QWebEnginePage.PermissionDeniedByUser) question = shared.feature_permission(url=url, option=options[feature], msg=messages[feature], yes_action=yes_action, no_action=no_action, abort_on=[self._abort_questions]) if question is not None: page.featurePermissionRequestCanceled.connect( functools.partial(self._on_feature_permission_cancelled, question, url, feature))
def __repr__(self): if self.modifiers is None: modifiers = None else: #modifiers = qflags_key(Qt, self.modifiers) modifiers = hex(int(self.modifiers)) return get_repr(self, constructor=True, key=debug.qenum_key(Qt, self.key), modifiers=modifiers, text=self.text)
def retry(self): state = self._qt_item.state() if state != QWebEngineDownloadItem.DownloadInterrupted: log.downloads.warning( "Refusing to retry download in state {}".format( debug.qenum_key(QWebEngineDownloadItem, state))) return self._qt_item.resume()
def acceptNavigationRequest(self, _frame, request, typ): """Override acceptNavigationRequest to handle clicked links. Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound to linkClicked won't work correctly, because when in a frameset, we have no idea in which frame the link should be opened. Checks if it should open it in a tab (middle-click or control) or not, and then opens the URL. Args: _frame: QWebFrame (target frame) request: QNetworkRequest typ: QWebPage::NavigationType """ url = request.url() urlstr = url.toDisplayString() if typ == QWebPage.NavigationTypeReload: self.reloading.emit(url) if typ != QWebPage.NavigationTypeLinkClicked: return True if not url.isValid(): message.error(self._win_id, "Invalid link {} clicked!".format(urlstr)) log.webview.debug(url.errorString()) self.open_target = usertypes.ClickTarget.normal return False tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) log.webview.debug("acceptNavigationRequest, url {}, type {}, hint " "target {}, open_target {}".format( urlstr, debug.qenum_key(QWebPage, typ), self._hint_target, self.open_target)) if self._hint_target is not None: target = self._hint_target else: target = self.open_target self.open_target = usertypes.ClickTarget.normal if target == usertypes.ClickTarget.tab: tabbed_browser.tabopen(url, False) return False elif target == usertypes.ClickTarget.tab_bg: tabbed_browser.tabopen(url, True) return False elif target == usertypes.ClickTarget.window: from qutebrowser.mainwindow import mainwindow window = mainwindow.MainWindow() window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) tabbed_browser.tabopen(url, False) return False else: return True
def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. This function is called from the createWindow() method of the associated QWebEnginePage, each time the page wants to create a new window of the given type. This might be the result, for example, of a JavaScript request to open a document in a new window. Args: wintype: This enum describes the types of window that can be created by the createWindow() function. QWebEnginePage::WebBrowserWindow: A complete web browser window. QWebEnginePage::WebBrowserTab: A web browser tab. QWebEnginePage::WebDialog: A window without decoration. QWebEnginePage::WebBrowserBackgroundTab: A web browser tab without hiding the current visible WebEngineView. Return: The new QWebEngineView object. """ debug_type = debug.qenum_key(QWebEnginePage, wintype) background_tabs = config.get('tabs', 'background-tabs') log.webview.debug("createWindow with type {}, background_tabs " "{}".format(debug_type, background_tabs)) if wintype == QWebEnginePage.WebBrowserWindow: # Shift-Alt-Click target = usertypes.ClickTarget.window elif wintype == QWebEnginePage.WebDialog: log.webview.warning("{} requested, but we don't support " "that!".format(debug_type)) target = usertypes.ClickTarget.tab elif wintype == QWebEnginePage.WebBrowserTab: # Middle-click / Ctrl-Click with Shift # FIXME:qtwebengine this also affects target=_blank links... if background_tabs: target = usertypes.ClickTarget.tab else: target = usertypes.ClickTarget.tab_bg elif wintype == QWebEnginePage.WebBrowserBackgroundTab: # Middle-click / Ctrl-Click if background_tabs: target = usertypes.ClickTarget.tab_bg else: target = usertypes.ClickTarget.tab else: raise ValueError("Invalid wintype {}".format(debug_type)) tab = shared.get_tab(self._win_id, target) return tab._widget # pylint: disable=protected-access
def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. This function is called from the createWindow() method of the associated QWebEnginePage, each time the page wants to create a new window of the given type. This might be the result, for example, of a JavaScript request to open a document in a new window. Args: wintype: This enum describes the types of window that can be created by the createWindow() function. QWebEnginePage::WebBrowserWindow: A complete web browser window. QWebEnginePage::WebBrowserTab: A web browser tab. QWebEnginePage::WebDialog: A window without decoration. QWebEnginePage::WebBrowserBackgroundTab: A web browser tab without hiding the current visible WebEngineView. (Added in Qt 5.7) Return: The new QWebEngineView object. """ debug_type = debug.qenum_key(QWebEnginePage, wintype) log.webview.debug("createWindow with type {}".format(debug_type)) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419 vercheck = qtutils.version_check qtbug_54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or qtutils.version_check('5.7.1') or os.environ.get('QUTE_QTBUG54419_PATCHED', '')) if not qtbug_54419_fixed: log.webview.debug("Ignoring createWindow because of QTBUG-54419") return None background = False if wintype in [ QWebEnginePage.WebBrowserWindow, QWebEnginePage.WebDialog ]: log.webview.warning("{} requested, but we don't support " "that!".format(debug_type)) elif wintype == QWebEnginePage.WebBrowserTab: pass elif (hasattr(QWebEnginePage, 'WebBrowserBackgroundTab') and wintype == QWebEnginePage.WebBrowserBackgroundTab): background = True else: raise ValueError("Invalid wintype {}".format(debug_type)) tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) # pylint: disable=protected-access return tabbed_browser.tabopen(background=background)._widget
def __repr__(self): # Meh, dependency cycle... from qutebrowser.utils.debug import qenum_key if self.modifiers is None: modifiers = None else: #modifiers = qflags_key(Qt, self.modifiers) modifiers = hex(int(self.modifiers)) return get_repr(self, constructor=True, key=qenum_key(Qt, self.key), modifiers=modifiers, text=self.text)
def acceptNavigationRequest(self, _frame, request, typ): """Override acceptNavigationRequest to handle clicked links. Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound to linkClicked won't work correctly, because when in a frameset, we have no idea in which frame the link should be opened. Checks if it should open it in a tab (middle-click or control) or not, and then opens the URL. Args: _frame: QWebFrame (target frame) request: QNetworkRequest typ: QWebPage::NavigationType """ url = request.url() urlstr = url.toDisplayString() if typ == QWebPage.NavigationTypeReload: self.reloading.emit(url) if typ != QWebPage.NavigationTypeLinkClicked: return True if not url.isValid(): message.error(self._win_id, "Invalid link {} clicked!".format( urlstr)) log.webview.debug(url.errorString()) self.open_target = usertypes.ClickTarget.normal return False tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) log.webview.debug("acceptNavigationRequest, url {}, type {}, hint " "target {}, open_target {}".format( urlstr, debug.qenum_key(QWebPage, typ), self._hint_target, self.open_target)) if self._hint_target is not None: target = self._hint_target else: target = self.open_target self.open_target = usertypes.ClickTarget.normal if target == usertypes.ClickTarget.tab: tabbed_browser.tabopen(url, False) return False elif target == usertypes.ClickTarget.tab_bg: tabbed_browser.tabopen(url, True) return False elif target == usertypes.ClickTarget.window: from qutebrowser.mainwindow import mainwindow window = mainwindow.MainWindow() window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) tabbed_browser.tabopen(url, False) return False else: return True
def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. This function is called from the createWindow() method of the associated QWebEnginePage, each time the page wants to create a new window of the given type. This might be the result, for example, of a JavaScript request to open a document in a new window. Args: wintype: This enum describes the types of window that can be created by the createWindow() function. QWebEnginePage::WebBrowserWindow: A complete web browser window. QWebEnginePage::WebBrowserTab: A web browser tab. QWebEnginePage::WebDialog: A window without decoration. QWebEnginePage::WebBrowserBackgroundTab: A web browser tab without hiding the current visible WebEngineView. (Added in Qt 5.7) Return: The new QWebEngineView object. """ debug_type = debug.qenum_key(QWebEnginePage, wintype) log.webview.debug("createWindow with type {}".format(debug_type)) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419 vercheck = qtutils.version_check qtbug_54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or qtutils.version_check('5.7.1') or os.environ.get('QUTE_QTBUG54419_PATCHED', '')) if not qtbug_54419_fixed: log.webview.debug("Ignoring createWindow because of QTBUG-54419") return None background = False if wintype in [QWebEnginePage.WebBrowserWindow, QWebEnginePage.WebDialog]: log.webview.warning("{} requested, but we don't support " "that!".format(debug_type)) elif wintype == QWebEnginePage.WebBrowserTab: pass elif (hasattr(QWebEnginePage, 'WebBrowserBackgroundTab') and wintype == QWebEnginePage.WebBrowserBackgroundTab): background = True else: raise ValueError("Invalid wintype {}".format(debug_type)) tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) # pylint: disable=protected-access return tabbed_browser.tabopen(background=background)._widget
def _writable_location(typ): """Wrapper around QStandardPaths.writableLocation.""" with qtutils.unset_organization(): path = QStandardPaths.writableLocation(typ) typ_str = debug.qenum_key(QStandardPaths, typ) log.misc.debug("writable location for {}: {}".format(typ_str, path)) if not path: raise EmptyValueError("QStandardPaths returned an empty value!") # Qt seems to use '/' as path separator even on Windows... path = path.replace('/', os.sep) return path
def _on_reply_error(self, code): """Handle QNetworkReply errors.""" if code == QNetworkReply.OperationCanceledError: return if self._reply is None: error = "Unknown error: {}".format( debug.qenum_key(QNetworkReply, code)) else: error = self._reply.errorString() self._die(error)
def interceptRequest(self, info): """Handle the given request. Reimplementing this virtual function and setting the interceptor on a profile makes it possible to intercept URL requests. This function is executed on the IO thread, and therefore running long tasks here will block networking. info contains the information about the URL request and will track internally whether its members have been altered. Args: info: QWebEngineUrlRequestInfo &info """ if 'log-requests' in self._args.debug_flags: resource_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.resourceType()) navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.navigationType()) log.webview.debug("{} {}, first-party {}, resource {}, " "navigation {}".format( bytes(info.requestMethod()).decode('ascii'), info.requestUrl().toDisplayString(), info.firstPartyUrl().toDisplayString(), resource_type, navigation_type)) url = info.requestUrl() # FIXME:qtwebengine only block ads for NavigationTypeOther? if self._host_blocker.is_blocked(url): log.webview.info("Request to {} blocked by host blocker.".format( url.host())) info.block(True) for header, value in shared.custom_headers(url=url): info.setHttpHeader(header, value) user_agent = config.instance.get('content.headers.user_agent', url=url) if user_agent is not None: info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
def _verify_message( self, msg: QDBusMessage, expected_signature: str, expected_type: QDBusMessage.MessageType, ) -> None: """Check the signature/type of a received message. Raises DBusError if the signature doesn't match. """ assert expected_type not in [ QDBusMessage.ErrorMessage, QDBusMessage.InvalidMessage, ], expected_type if msg.type() == QDBusMessage.ErrorMessage: err = msg.errorName() if err == "org.freedesktop.DBus.Error.NoReply": self.error.emit( msg.errorMessage()) # notification daemon is gone return raise Error(f"Got DBus error: {err} - {msg.errorMessage()}") signature = msg.signature() if signature != expected_signature: raise Error( f"Got a message with signature {signature} but expected " f"{expected_signature} (args: {msg.arguments()})") typ = msg.type() if typ != expected_type: type_str = debug.qenum_key(QDBusMessage.MessageType, typ) expected_type_str = debug.qenum_key(QDBusMessage.MessageType, expected_type) raise Error( f"Got a message of type {type_str} but expected {expected_type_str}" f"(args: {msg.arguments()})")
def retry(self): state = self._qt_item.state() if state != QWebEngineDownloadItem.DownloadInterrupted: log.downloads.warning( "Trying to retry download in state {}".format( debug.qenum_key(QWebEngineDownloadItem, state))) return try: self._qt_item.resume() except AttributeError: raise downloads.UnsupportedOperationError( "Retrying downloads is unsupported with QtWebEngine on " "Qt/PyQt < 5.10")
def acceptNavigationRequest(self, _frame: QWebFrame, request: QNetworkRequest, typ: QWebPage.NavigationType): """Override acceptNavigationRequest to handle clicked links. Setting linkDelegationPolicy to DelegateAllLinks and using a slot bound to linkClicked won't work correctly, because when in a frameset, we have no idea in which frame the link should be opened. Checks if it should open it in a tab (middle-click or control) or not, and then conditionally opens the URL here or in another tab/window. """ url = request.url() log.webview.debug("navigation request: url {}, type {}, " "target {} override {}".format( url.toDisplayString(), debug.qenum_key(QWebPage, typ), self.open_target, self._tabdata.override_target)) if self._tabdata.override_target is not None: target = self._tabdata.override_target self._tabdata.override_target = None else: target = self.open_target if typ == QWebPage.NavigationTypeReload: self.reloading.emit(url) return True elif typ != QWebPage.NavigationTypeLinkClicked: return True if not url.isValid(): msg = urlutils.get_errstring(url, "Invalid link clicked") message.error(msg) self.open_target = usertypes.ClickTarget.normal return False if target == usertypes.ClickTarget.normal: return True tab = shared.get_tab(self._win_id, target) tab.openurl(url) self.open_target = usertypes.ClickTarget.normal return False
def acceptNavigationRequest(self, url: QUrl, typ: QWebEnginePage.NavigationType, is_main_frame: bool): """Override acceptNavigationRequest to handle clicked links. This only show an error on invalid links - everything else is handled in createWindow. """ log.webview.debug("navigation request: url {}, type {}, is_main_frame " "{}".format(url.toDisplayString(), debug.qenum_key(QWebEnginePage, typ), is_main_frame)) if (typ == QWebEnginePage.NavigationTypeLinkClicked and not url.isValid()): msg = urlutils.get_errstring(url, "Invalid link clicked") message.error(msg) return False return True
def __init__(self, msg, error): super().__init__(msg) self.error = error log.sql.debug("SQL error:") log.sql.debug("type: {}".format( debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(error.databaseText())) log.sql.debug("driver text: {}".format(error.driverText())) log.sql.debug("error code: {}".format(error.nativeErrorCode())) # https://sqlite.org/rescode.html # https://github.com/qutebrowser/qutebrowser/issues/2930 # https://github.com/qutebrowser/qutebrowser/issues/3004 environmental_errors = [ '5', # SQLITE_BUSY ("database is locked") '8', # SQLITE_READONLY '13', # SQLITE_FULL ] self.environmental = error.nativeErrorCode() in environmental_errors
def raise_sqlite_error(msg, error): """Raise either a BugError or KnownError.""" error_code = error.nativeErrorCode() database_text = error.databaseText() driver_text = error.driverText() log.sql.debug("SQL error:") log.sql.debug("type: {}".format( debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(database_text)) log.sql.debug("driver text: {}".format(driver_text)) log.sql.debug("error code: {}".format(error_code)) known_errors = [ SqliteErrorCode.BUSY, SqliteErrorCode.READONLY, SqliteErrorCode.IOERR, SqliteErrorCode.CORRUPT, SqliteErrorCode.FULL, SqliteErrorCode.CANTOPEN, ] # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70506 # We don't know what the actual error was, but let's assume it's not us to # blame... Usually this is something like an unreadable database file. qtbug_70506 = (error_code == SqliteErrorCode.UNKNOWN and driver_text == "Error opening database" and database_text == "out of memory") # https://github.com/qutebrowser/qutebrowser/issues/4681 # If the query we built was too long too_long_err = ( error_code == SqliteErrorCode.ERROR and driver_text == "Unable to execute statement" and (database_text.startswith("Expression tree is too large") or database_text == "too many SQL variables")) if error_code in known_errors or qtbug_70506 or too_long_err: raise KnownError(msg, error) raise BugError(msg, error)
def _on_feature_permission_requested(self, url, feature): """Ask the user for approval for geolocation/media/etc..""" options = { QWebEnginePage.Geolocation: ('content', 'geolocation'), QWebEnginePage.MediaAudioCapture: ('content', 'media-capture'), QWebEnginePage.MediaVideoCapture: ('content', 'media-capture'), QWebEnginePage.MediaAudioVideoCapture: ('content', 'media-capture'), } messages = { QWebEnginePage.Geolocation: 'access your location', QWebEnginePage.MediaAudioCapture: 'record audio', QWebEnginePage.MediaVideoCapture: 'record video', QWebEnginePage.MediaAudioVideoCapture: 'record audio/video', } assert options.keys() == messages.keys() if feature not in options: log.webview.error("Unhandled feature permission {}".format( debug.qenum_key(QWebEnginePage, feature))) self.setFeaturePermission(url, feature, QWebEnginePage.PermissionDeniedByUser) return yes_action = functools.partial( self.setFeaturePermission, url, feature, QWebEnginePage.PermissionGrantedByUser) no_action = functools.partial( self.setFeaturePermission, url, feature, QWebEnginePage.PermissionDeniedByUser) question = shared.feature_permission( url=url, option=options[feature], msg=messages[feature], yes_action=yes_action, no_action=no_action, abort_on=[self.shutting_down, self.loadStarted]) if question is not None: self.featurePermissionRequestCanceled.connect( functools.partial(self._on_feature_permission_cancelled, question, url, feature))
def test_reconverted(self): """Test passing a flag value which was re-converted to an enum.""" # FIXME maybe this should return the right thing anyways? debug.qenum_key(Qt, Qt.Alignment(int(Qt.AlignLeft)))
def __repr__(self): return utils.get_repr( self, error=debug.qenum_key(QSslError, self.error()), string=self.errorString())
def test_int(self): """Test passing an int with explicit klass given.""" key = debug.qenum_key(QFrame, 0x0030, klass=QFrame.Shadow) self.assertEqual(key, 'Sunken')
def test_unknown(self): """Test passing an unknown value.""" key = debug.qenum_key(QFrame, 0x1337, klass=QFrame.Shadow) self.assertEqual(key, '0x1337')
def test_qenum_key(self, base, value, klass, expected): key = debug.qenum_key(base, value, klass=klass) assert key == expected
def test_add_base(self): key = debug.qenum_key(QFrame, QFrame.Sunken, add_base=True) assert key == 'QFrame.Sunken'
def test_int_noklass(self): """Test passing an int without explicit klass given.""" with pytest.raises(TypeError): debug.qenum_key(QFrame, 42)