Exemple #1
0
 def addHistoryEntry(self, url):
     """
     Public method to add a history entry.
     
     @param url URL to be added (string)
     """
     cleanurl = QUrl(url)
     if cleanurl.scheme() not in ["eric", "about"]:
         if cleanurl.password():
             # don't save the password in the history
             cleanurl.setPassword("")
         if cleanurl.host():
             cleanurl.setHost(cleanurl.host().lower())
         itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime())
         self._addHistoryEntry(itm)
Exemple #2
0
 def on_imagesTree_currentItemChanged(self, current, previous):
     """
     Private slot to show a preview of the selected image.
     
     @param current current image entry (QTreeWidgetItem)
     @param previous old current entry (QTreeWidgetItem)
     """
     if current is None:
         return
     
     imageUrl = QUrl(current.text(1))
     if not imageUrl.host():
         imageUrl.setHost(QUrl(self.siteAddressLabel.text()).host())
         imageUrl.setScheme(QUrl(self.siteAddressLabel.text()).scheme())
     
     import Helpviewer.HelpWindow
     cache = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().cache()
     if cache:
         cacheData = cache.data(imageUrl)
     else:
         cacheData = None
     pixmap = QPixmap()
     invalidPixmap = False
     scene = QGraphicsScene(self.imagePreview)
     if not cacheData:
         invalidPixmap = True
     else:
         pixmap.loadFromData(cacheData.readAll())
         if pixmap.isNull():
             invalidPixmap = True
     if invalidPixmap:
         scene.addText(self.tr("Preview not available."))
     else:
         scene.addPixmap(pixmap)
     self.imagePreview.setScene(scene)
Exemple #3
0
 def keyPressEvent(self, evt):
     """
     Protected method to handle key presses.
     
     @param evt reference to the key press event (QKeyEvent)
     """
     if evt.key() == Qt.Key_Escape and self.__browser is not None:
         self.setText(
             str(self.__browser.url().toEncoded(), encoding="utf-8"))
         self.selectAll()
         return
     
     currentText = self.text().strip()
     if evt.key() in [Qt.Key_Enter, Qt.Key_Return] and \
        not currentText.lower().startswith("http://"):
         append = ""
         if evt.modifiers() == Qt.KeyboardModifiers(Qt.ControlModifier):
             append = ".com"
         elif evt.modifiers() == Qt.KeyboardModifiers(
                 Qt.ControlModifier | Qt.ShiftModifier):
             append = ".org"
         elif evt.modifiers() == Qt.KeyboardModifiers(Qt.ShiftModifier):
             append = ".net"
         
         if append != "":
             url = QUrl("http://www." + currentText)
             host = url.host()
             if not host.lower().endswith(append):
                 host += append
                 url.setHost(host)
                 self.setText(url.toString())
     
     E5LineEdit.keyPressEvent(self, evt)
Exemple #4
0
 def __guessUrlFromPath(self, path):
     """
     Private method to guess an URL given a path string.
     
     @param path path string to guess an URL for (string)
     @return guessed URL (QUrl)
     """
     manager = self.__mainWindow.openSearchManager()
     path = Utilities.fromNativeSeparators(path)
     url = manager.convertKeywordSearchToUrl(path)
     if url.isValid():
         return url
     
     try:
         url = QUrl.fromUserInput(path)
     except AttributeError:
         url = QUrl(path)
     
     if url.scheme() == "about" and \
        url.path() == "home":
         url = QUrl("eric:home")
     
     if url.scheme() in ["s", "search"]:
         url = manager.currentEngine().searchUrl(url.path().strip())
     
     if url.scheme() != "" and \
        (url.host() != "" or url.path() != ""):
         return url
     
     urlString = Preferences.getHelp("DefaultScheme") + path.strip()
     url = QUrl.fromEncoded(urlString.encode("utf-8"), QUrl.TolerantMode)
     
     return url
def download_temp(url: QUrl) -> TempDownload:
    """Download the given URL into a file object.

    The download is not saved to disk.

    Returns a ``TempDownload`` object, which triggers a ``finished`` signal
    when the download has finished::

        dl = downloads.download_temp(QUrl("https://www.example.com/"))
        dl.finished.connect(functools.partial(on_download_finished, dl))

    After the download has finished, its ``successful`` attribute can be
    checked to make sure it finished successfully. If so, its contents can be
    read by accessing the ``fileobj`` attribute::

        def on_download_finished(download: downloads.TempDownload) -> None:
            if download.successful:
                print(download.fileobj.read())
                download.fileobj.close()
    """
    fobj = io.BytesIO()
    fobj.name = 'temporary: ' + url.host()
    target = downloads.FileObjDownloadTarget(fobj)
    download_manager = objreg.get('qtnetwork-download-manager')
    return download_manager.get(url, target=target, auto_remove=True)
Exemple #6
0
 def __addFeed(self):
     """
     Private slot to add a RSS feed.
     """
     button = self.sender()
     urlString = button.feed[1]
     url = QUrl(urlString)
     if not url.host():
         if not urlString.startswith("/"):
             urlString = "/" + urlString
         urlString = self.__browser.url().host() + urlString
         tmpUrl = QUrl(urlString)
         if not tmpUrl.scheme():
             urlString = "http://" + urlString
         tmpUrl = QUrl(urlString)
         if not tmpUrl.scheme() or not tmpUrl.host():
             return
     if not url.isValid():
         return
     
     if button.feed[0]:
         title = button.feed[0]
     else:
         title = self.__browser.url().host()
     
     import Helpviewer.HelpWindow
     feedsManager = Helpviewer.HelpWindow.HelpWindow.feedsManager()
     if feedsManager.addFeed(urlString, title, self.__browser.icon()):
         if Helpviewer.HelpWindow.HelpWindow.notificationsEnabled():
             Helpviewer.HelpWindow.HelpWindow.showNotification(
                 UI.PixmapCache.getPixmap("rss48.png"),
                 self.tr("Add RSS Feed"),
                 self.tr("""The feed was added successfully."""))
         else:
             E5MessageBox.information(
                 self,
                 self.tr("Add RSS Feed"),
                 self.tr("""The feed was added successfully."""))
     else:
         E5MessageBox.warning(
             self,
             self.tr("Add RSS Feed"),
             self.tr("""The feed was already added before."""))
         
     self.close()
 def __showEnginesMenu(self):
     """
     Private slot to handle the display of the engines menu.
     """
     self.__enginesMenu.clear()
     
     from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction
     engineNames = self.__openSearchManager.allEnginesNames()
     for engineName in engineNames:
         engine = self.__openSearchManager.engine(engineName)
         action = OpenSearchEngineAction(engine, self.__enginesMenu)
         action.setData(engineName)
         action.triggered.connect(self.__changeCurrentEngine)
         self.__enginesMenu.addAction(action)
         
         if self.__openSearchManager.currentEngineName() == engineName:
             action.setCheckable(True)
             action.setChecked(True)
     
     ct = self.__mw.currentBrowser()
     linkedResources = ct.linkedResources("search")
     
     if len(linkedResources) > 0:
         self.__enginesMenu.addSeparator()
     
     for linkedResource in linkedResources:
         url = QUrl(linkedResource.href)
         title = linkedResource.title
         mimetype = linkedResource.type_
         
         if mimetype != "application/opensearchdescription+xml":
             continue
         if url.isEmpty():
             continue
         
         if url.isRelative():
             url = ct.url().resolved(url)
         
         if not title:
             if not ct.title():
                 title = url.host()
             else:
                 title = ct.title()
         
         action = self.__enginesMenu.addAction(
             self.tr("Add '{0}'").format(title),
             self.__addEngineFromUrl)
         action.setData(url)
         action.setIcon(ct.icon())
     
     self.__enginesMenu.addSeparator()
     self.__enginesMenu.addAction(self.__mw.searchEnginesAction())
     
     if self.__recentSearches:
         self.__enginesMenu.addAction(self.tr("Clear Recent Searches"),
                                      self.clear)
Exemple #8
0
def data_for_url(url):
    """Get the data to show for the given URL.

    Args:
        url: The QUrl to show.

    Return:
        A (mimetype, data) tuple.
    """
    norm_url = url.adjusted(QUrl.NormalizePathSegments |
                            QUrl.StripTrailingSlash)
    if norm_url != url:
        raise Redirect(norm_url)

    path = url.path()
    host = url.host()
    query = urlutils.query_string(url)
    # A url like "qute:foo" is split as "scheme:path", not "scheme:host".
    log.misc.debug("url: {}, path: {}, host {}".format(
        url.toDisplayString(), path, host))
    if not path or not host:
        new_url = QUrl()
        new_url.setScheme('qute')
        # When path is absent, e.g. qute://help (with no trailing slash)
        if host:
            new_url.setHost(host)
        # When host is absent, e.g. qute:help
        else:
            new_url.setHost(path)

        new_url.setPath('/')
        if query:
            new_url.setQuery(query)
        if new_url.host():  # path was a valid host
            raise Redirect(new_url)

    try:
        handler = _HANDLERS[host]
    except KeyError:
        raise NoHandlerFound(url)

    try:
        mimetype, data = handler(url)
    except OSError as e:
        # FIXME:qtwebengine how to handle this?
        raise QuteSchemeOSError(e)
    except QuteSchemeError as e:
        raise

    assert mimetype is not None, url
    if mimetype == 'text/html' and isinstance(data, str):
        # We let handlers return HTML as text
        data = data.encode('utf-8', errors='xmlcharrefreplace')

    return mimetype, data
Exemple #9
0
 def __saveImage(self):
     """
     Private slot to save the selected image to disk.
     """
     act = self.sender()
     index = act.data()
     itm = self.imagesTree.topLevelItem(index)
     if itm is None:
         return
     
     imageUrl = QUrl(itm.text(1))
     if not imageUrl.host():
         imageUrl.setHost(QUrl(self.siteAddressLabel.text()).host())
         imageUrl.setScheme(QUrl(self.siteAddressLabel.text()).scheme())
     
     import Helpviewer.HelpWindow
     cache = Helpviewer.HelpWindow.HelpWindow.networkAccessManager().cache()
     if cache:
         cacheData = cache.data(imageUrl)
     else:
         cacheData = None
     if not cacheData:
         E5MessageBox.critical(
             self,
             self.tr("Save Image"),
             self.tr("""This image is not available."""))
         return
     
     downloadDirectory = Helpviewer.HelpWindow.HelpWindow\
         .downloadManager().downloadDirectory()
     fn = os.path.join(downloadDirectory, os.path.basename(itm.text(1)))
     filename = E5FileDialog.getSaveFileName(
         self,
         self.tr("Save Image"),
         fn,
         self.tr("All Files (*)"),
         E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
     
     if not filename:
         return
     
     f = QFile(filename)
     if not f.open(QFile.WriteOnly):
         E5MessageBox.critical(
             self,
             self.tr("Save Image"),
             self.tr(
                 """<p>Cannot write to file <b>{0}</b>.</p>""")
             .format(filename))
         return
     f.write(cacheData.readAll())
     f.close()
Exemple #10
0
    def _is_blocked(self, request_url: QUrl,
                    first_party_url: QUrl = None) -> bool:
        """Check whether the given request is blocked."""
        if first_party_url is not None and not first_party_url.isValid():
            first_party_url = None

        if not config.get('content.host_blocking.enabled',
                          url=first_party_url):
            return False

        host = request_url.host()
        return ((host in self._blocked_hosts or
                 host in self._config_blocked_hosts) and
                not _is_whitelisted_url(request_url))
Exemple #11
0
def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS, whitelisted=WHITELISTED_HOSTS, urls_to_check=URLS_TO_CHECK):
    """Test if Urls to check are blocked or not by HostBlocker.

    Ensure URLs in 'blocked' and not in 'whitelisted' are blocked.
    All other URLs must not be blocked.
    """
    whitelisted = list(whitelisted) + list(host_blocker.WHITELISTED)
    for str_url in urls_to_check:
        url = QUrl(str_url)
        host = url.host()
        if host in blocked and host not in whitelisted:
            assert host_blocker.is_blocked(url)
        else:
            assert not host_blocker.is_blocked(url)
def test_disabled_blocking_per_url(config_stub, host_blocker_factory):
    example_com = 'https://www.example.com/'

    config_stub.val.content.host_blocking.lists = []
    pattern = urlmatch.UrlPattern(example_com)
    config_stub.set_obj('content.host_blocking.enabled', False,
                        pattern=pattern)

    url = QUrl('blocked.example.com')

    host_blocker = host_blocker_factory()
    host_blocker._blocked_hosts.add(url.host())

    assert host_blocker._is_blocked(url)
    assert not host_blocker._is_blocked(url, first_party_url=QUrl(example_com))
Exemple #13
0
 def __setOkButton(self):
     """
     Private slot to enable or disable the OK button.
     """
     enable = True
     
     enable = enable and bool(self.titleEdit.text())
     
     urlString = self.urlEdit.text()
     enable = enable and bool(urlString)
     if urlString:
         url = QUrl(urlString)
         enable = enable and bool(url.scheme())
         enable = enable and bool(url.host())
     
     self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
Exemple #14
0
 def __init__(self, connections=[], parent=None):
     """
     Constructor
     
     @param connections list of database connections to add
         (list of strings)
     @param parent reference to the parent widget (QWidget)
     """
     super(SqlBrowser, self).__init__(parent)
     self.setObjectName("SqlBrowser")
     
     self.setWindowTitle(self.tr("SQL Browser"))
     self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))
     
     self.setStyle(Preferences.getUI("Style"),
                   Preferences.getUI("StyleSheet"))
     
     from .SqlBrowserWidget import SqlBrowserWidget
     self.__browser = SqlBrowserWidget(self)
     self.setCentralWidget(self.__browser)
     
     self.__browser.statusMessage.connect(self.statusBar().showMessage)
     
     self.__initActions()
     self.__initMenus()
     self.__initToolbars()
     
     self.resize(self.__browser.size())
     
     self.__warnings = []
     
     for connection in connections:
         url = QUrl(connection, QUrl.TolerantMode)
         if not url.isValid():
             self.__warnings.append(
                 self.tr("Invalid URL: {0}").format(connection))
             continue
         
         err = self.__browser.addConnection(url.scheme(), url.path(),
                                            url.userName(), url.password(),
                                            url.host(), url.port(-1))
         if err.type() != QSqlError.NoError:
             self.__warnings.append(
                 self.tr("Unable to open connection: {0}".format(
                     err.text())))
     
     QTimer.singleShot(0, self.__uiStartUp)
Exemple #15
0
 def transform(self, value):
     if not value:
         return None
     elif value == 'system':
         return SYSTEM_PROXY
     elif value == 'none':
         return QNetworkProxy(QNetworkProxy.NoProxy)
     url = QUrl(value)
     typ = self.PROXY_TYPES[url.scheme()]
     proxy = QNetworkProxy(typ, url.host())
     if url.port() != -1:
         proxy.setPort(url.port())
     if url.userName():
         proxy.setUser(url.userName())
     if url.password():
         proxy.setPassword(url.password())
     return proxy
def assert_urls(host_blocker, blocked=BLOCKLIST_HOSTS,
                whitelisted=WHITELISTED_HOSTS, urls_to_check=URLS_TO_CHECK):
    """Test if Urls to check are blocked or not by HostBlocker.

    Ensure URLs in 'blocked' and not in 'whitelisted' are blocked.
    All other URLs must not be blocked.

    localhost is an example of a special case that shouldn't be blocked.
    """
    whitelisted = list(whitelisted) + ['localhost']
    for str_url in urls_to_check:
        url = QUrl(str_url)
        host = url.host()
        if host in blocked and host not in whitelisted:
            assert host_blocker._is_blocked(url)
        else:
            assert not host_blocker._is_blocked(url)
def data_for_url(url):
    """Get the data to show for the given URL.

    Args:
        url: The QUrl to show.

    Return:
        A (mimetype, data) tuple.
    """
    path = url.path()
    host = url.host()
    # A url like "qute:foo" is split as "scheme:path", not "scheme:host".
    log.misc.debug("url: {}, path: {}, host {}".format(
        url.toDisplayString(), path, host))
    if path and not host:
        new_url = QUrl()
        new_url.setScheme('qute')
        new_url.setHost(path)
        new_url.setPath('/')
        if new_url.host():  # path was a valid host
            raise Redirect(new_url)

    try:
        handler = _HANDLERS[host]
    except KeyError:
        raise NoHandlerFound(url)

    try:
        mimetype, data = handler(url)
    except OSError as e:
        # FIXME:qtwebengine how to handle this?
        raise QuteSchemeOSError(e)
    except QuteSchemeError as e:
        raise

    assert mimetype is not None, url
    if mimetype == 'text/html' and isinstance(data, str):
        # We let handlers return HTML as text
        data = data.encode('utf-8', errors='xmlcharrefreplace')

    return mimetype, data
Exemple #18
0
    def _init_host(self, parsed):
        """Parse the host from the given URL.

        Deviation from Chromium:
        - http://:1234/ is not a valid URL because it has no host.
        """
        if parsed.hostname is None or not parsed.hostname.strip():
            if self._scheme not in self._SCHEMES_WITHOUT_HOST:
                raise ParseError("Pattern without host")
            assert self._host is None
            return

        if parsed.netloc.startswith('['):
            # Using QUrl parsing to minimize ipv6 addresses
            url = QUrl()
            url.setHost(parsed.hostname)
            if not url.isValid():
                raise ParseError(url.errorString())
            self._host = url.host()
            return

        # FIXME what about multiple dots?
        host_parts = parsed.hostname.rstrip('.').split('.')
        if host_parts[0] == '*':
            host_parts = host_parts[1:]
            self._match_subdomains = True

        if not host_parts:
            self._host = None
            return

        self._host = '.'.join(host_parts)

        if self._host.endswith('.*'):
            # Special case to have a nicer error
            raise ParseError("TLD wildcards are not implemented yet")
        if '*' in self._host:
            # Only * or *.foo is allowed as host.
            raise ParseError("Invalid host wildcard")
Exemple #19
0
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = "OraDocs by littleT"
        self.left = 10
        self.top = 10
        self.width = 600
        self.height = 400
        self.initUI()

        self.url = QUrl()
        self.qnam = QNetworkAccessManager()
        self.reply = None
        self.outFile = None
        self.httpGetId = 0
        self.httpRequestAborted = False

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.statusBar().showMessage('Starting ...')
        # for the menu first create some actions to be added like
        # File - Exit action
        exitAction = QAction(QIcon('exit24.png'), 'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit Application')
        exitAction.triggered.connect(self.close)
        # File - open
        openAction = QAction(QIcon(''), '&Open', self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Opens a file')
        openAction.triggered.connect(self.openCall())
        # the help actions
        helpAction = QAction(QIcon(''), 'Help', self)
        helpAction.triggered.connect(self.openHelp)
        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu('&File')
        fileMenu.addAction(openAction)
        fileMenu.addAction(exitAction)
        helpMenu = mainMenu.addMenu('&Help')
        helpMenu.addAction(helpAction)

        self.logOutput = QTextEdit(self)
        self.logOutput.setReadOnly(True)
        # self.logOutput.setLineWrapMode(QTextEdit.NoWrap)
        self.logOutput.setGeometry(5, 200, 590, 60)

        self.statusBar().showMessage('waiting for click ...')
        self.urlLineEdit = QLineEdit(self)
        # self.textbox.move(5, 300)
        # self.textbox.resize(590, 20)
        self.urlLineEdit.setGeometry(5, 325, 590, 20)

        self.button = QPushButton('Click Me', self)
        self.button.move(10, 350)
        self.button.clicked.connect(self.doRequest)


    def doRequest(self):
        self.root = "https://oradocs-corp.documents.us2.oraclecloud.com/documents/"
        # url = "https://www.google.com"
        self.url = QUrl(self.root)

        self.httpRequestAborted = False
        self.startRequest(self.url)
        self.downloadFile()


        self.qnam.authenticationRequired.connect(
                self.slotAuthenticationRequired)

    def startRequest(self, url):
        self.reply = self.qnam.get(QNetworkRequest(url))
        self.reply.finished.connect(self.httpFinished)
        self.reply.readyRead.connect(self.httpReadyRead)

    def downloadFile(self):
        self.url = QUrl(self.urlLineEdit.text())
        fileInfo = QFileInfo(self.url.path())
        fileName = fileInfo.fileName()

        if not fileName:
            fileName = 'index.html'

        if QFile.exists(fileName):
            ret = QMessageBox.question(self, "HTTP",
                    "There already exists a file called %s in the current "
                    "directory. Overwrite?" % fileName,
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

            if ret == QMessageBox.No:
                return

            QFile.remove(fileName)

        self.outFile = QFile(fileName)
        if not self.outFile.open(QIODevice.WriteOnly):
            QMessageBox.information(self, "HTTP",
                    "Unable to save the file %s: %s." % (fileName, self.outFile.errorString()))
            self.outFile = None
            return

        self.progressDialog.setWindowTitle("HTTP")
        self.progressDialog.setLabelText("Downloading %s." % fileName)
        self.downloadButton.setEnabled(False)

        self.httpRequestAborted = False
        self.startRequest(self.url)

    def cancelDownload(self):
        self.statusLabel.setText("Download canceled.")
        self.httpRequestAborted = True
        if self.reply is not None:
            self.reply.abort()
        self.downloadButton.setEnabled(True)

    def httpFinished(self):
        if self.httpRequestAborted:
            if self.outFile is not None:
                self.outFile.close()
                self.outFile.remove()
                self.outFile = None

            self.reply.deleteLater()
            self.reply = None
            # self.progressDialog.hide()
            return

        # self.progressDialog.hide()
        self.outFile.flush()
        self.outFile.close()

        redirectionTarget = self.reply.attribute(QNetworkRequest.RedirectionTargetAttribute)

        if self.reply.error():
            self.outFile.remove()
            QMessageBox.information(self, "HTTP",
                    "Download failed: %s." % self.reply.errorString())
            self.downloadButton.setEnabled(True)
        elif redirectionTarget is not None:
            newUrl = self.url.resolved(redirectionTarget)

            ret = QMessageBox.question(self, "HTTP",
                    "Redirect to %s?" % newUrl.toString(),
                    QMessageBox.Yes | QMessageBox.No)

            if ret == QMessageBox.Yes:
                self.url = newUrl
                self.reply.deleteLater()
                self.reply = None
                self.outFile.open(QIODevice.WriteOnly)
                self.outFile.resize(0)
                self.startRequest(self.url)
                return
        else:
            fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName()
            self.statusLabel.setText("Downloaded %s to %s." % (fileName, QDir.currentPath()))

            self.downloadButton.setEnabled(True)

        self.reply.deleteLater()
        self.reply = None
        self.outFile = None

    def httpReadyRead(self):
        if self.outFile is not None:
            self.outFile.write(self.reply.readAll())

    def updateDataReadProgress(self, bytesRead, totalBytes):
        if self.httpRequestAborted:
            return

        self.progressDialog.setMaximum(totalBytes)
        self.progressDialog.setValue(bytesRead)

    def slotAuthenticationRequired(self, authenticator):
        import os
        from PyQt5 import uic

        ui = os.path.join(os.path.dirname(__file__), 'authenticationdialog.ui')
        dlg = uic.loadUi(ui)
        dlg.adjustSize()
        dlg.siteDescription.setText("%s at %s" % (authenticator.realm(), self.url.host()))

        dlg.userEdit.setText(self.url.userName())
        dlg.passwordEdit.setText(self.url.password())

        if dlg.exec_() == QDialog.Accepted:
            authenticator.setUser(dlg.userEdit.text())
            authenticator.setPassword(dlg.passwordEdit.text())

    def sslErrors(self, reply, errors):
        errorString = ", ".join([str(error.errorString()) for error in errors])

        ret = QMessageBox.warning(self, "HTTP Example",
                "One or more SSL errors has occurred: %s" % errorString,
                QMessageBox.Ignore | QMessageBox.Abort)

        if ret == QMessageBox.Ignore:
            self.reply.ignoreSslErrors()

    def openCall(self):
        print("Open Call")

    def openHelp(self):
        print("Open Help")

    @pyqtSlot()
    def onClick(self):
        # textboxValue = self.textbox.text()
        # QMessageBox.question(self, 'Hello World!', "Confirm: " + textboxValue,
        #                     QMessageBox.Ok, QMessageBox.Ok)
        # self.textbox.setText("...")
        myRet = self.myDocs.doRequest("https://www.google.com")
        self.logOutput.moveCursor(QTextCursor.End)
        print(myRet)
        self.logOutput.insertPlainText(myRet)

    @pyqtSlot()
    def processReq(self):
        if self.nam.bytesAvailable():
            self.logOutput.moveCursor(QTextCursor.End)
            self.logOutput.insertPlainText('We are connected\n')
            bytes_string = self.nam.readAll()

        self.nam.deleteLater()

    @pyqtSlot(QNetworkReply.NetworkError)
    def processErr(self, code):
        self.msg.critical(None, "Info", "You are not connected to the Internet.")
        print(code)
Exemple #20
0
class ClickToFlash(QWidget, Ui_ClickToFlash):
    """
    Class implementing the Flash blocker.
    """
    _acceptedUrl = QUrl()
    _acceptedArgNames = []
    _acceptedArgValues = []

    def __init__(self, plugin, mimeType, url, argumentNames, argumentValues,
                 parent=None):
        """
        Constructor
        
        @param plugin reference to the plug-in (ClickToFlashPlugin)
        @param mimeType MIME type for the plug-in (string)
        @param url requested URL (QUrl)
        @param argumentNames list of argument names (list of strings)
        @param argumentValues list of argument values (list of strings)
        @param parent reference to the parent widget (QWidget)
        """
        super(ClickToFlash, self).__init__(parent)
        
        # Check AdBlock first
        import Helpviewer.HelpWindow
        manager = Helpviewer.HelpWindow.HelpWindow.adBlockManager()
        if manager.isEnabled():
            urlString = bytes(url.toEncoded()).decode()
            urlDomain = url.host()
            for subscription in manager.subscriptions():
                blockedRule = subscription.match(
                    QNetworkRequest(url), urlDomain, urlString)
                if blockedRule:
                    QTimer.singleShot(200, self.__hideAdBlocked)
                    return
        
        self.setupUi(self)
        
        self.__swapping = False
        self.__element = QWebElement()
        
        self.__plugin = plugin
        self.__url = QUrl(url)
        self.__argumentNames = argumentNames[:]
        self.__argumentValues = argumentValues[:]
        self.__mimeType = mimeType
        
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.__showContextMenu)
        self.setToolTip(self.__url.toString())
        
        iconName = plugin.getIconName(mimeType)
        if iconName:
            self.loadFlashButton.setIcon(UI.PixmapCache.getIcon(iconName))
        else:
            self.loadFlashButton.setText(self.tr("Load"))
    
    @pyqtSlot()
    def on_loadFlashButton_clicked(self):
        """
        Private slot handling the flash activation.
        """
        self.__load()
    
    def __showContextMenu(self):
        """
        Private slot to show the context menu.
        """
        menu = QMenu()
        act = menu.addAction(self.tr("Object blocked by ClickToFlash"))
        font = act.font()
        font.setBold(True)
        act.setFont(font)
        menu.addAction(
            self.tr("Show information about object"), self.__showInfo)
        menu.addSeparator()
        menu.addAction(self.tr("Load"), self.__load)
        menu.addAction(self.tr("Delete object"), self.__hideAdBlocked)
        menu.addSeparator()
        host = self.__url.host()
        add = menu.addAction(
            self.tr("Add '{0}' to Whitelist").format(host),
            self.__addToWhitelist)
        remove = menu.addAction(
            self.tr("Remove '{0}' from Whitelist").format(host),
            self.__removeFromWhitelist)
        onWhitelist = self.__plugin.onWhitelist(host)
        add.setEnabled(not onWhitelist)
        remove.setEnabled(onWhitelist)
        menu.addSeparator()
        menu.addAction(self.tr("Configure Whitelist"), self.__configure)
        menu.actions()[0].setEnabled(False)
        
        menu.exec_(QCursor.pos())
    
    def swapping(self):
        """
        Public method to check, if the plug-in is swapping.
        
        @return flag indicating the swapping status (boolean)
        """
        return self.__swapping
    
    def __configure(self):
        """
        Private slot to configure the whitelist.
        """
        self.__plugin.configure()
    
    def __addToWhitelist(self):
        """
        Private slot to add the host to the whitelist.
        """
        self.__plugin.addToWhitelist(self.__url.host())
    
    def __removeFromWhitelist(self):
        """
        Private slot to remove the host from the whitelist.
        """
        self.__plugin.removeFromWhitelist(self.__url.host())
    
    def __load(self, all=False):
        """
        Private slot to load the flash content.
        
        @param all flag indicating to load all flash players. (boolean)
        """
        self.__findElement()
        if not self.__element.isNull():
            substitute = self.__element.clone()
            substitute.setAttribute("type", self.__mimeType)
            self.__element.replace(substitute)

            ClickToFlash._acceptedUrl = self.__url
            ClickToFlash._acceptedArgNames = self.__argumentNames
            ClickToFlash._acceptedArgValues = self.__argumentValues
    
    def __findElement(self):
        """
        Private method to find the element belonging to this ClickToFlash
        instance.
        """
        parent = self.parentWidget()
        view = None
        while parent is not None:
            if isinstance(parent, QWebView):
                view = parent
                break
            parent = parent.parentWidget()
        if view is None:
            return
        
        objectPos = view.mapFromGlobal(self.loadFlashButton.mapToGlobal(
            self.loadFlashButton.pos()))
        objectFrame = view.page().frameAt(objectPos)
        hitResult = QWebHitTestResult()
        hitElement = QWebElement()
        
        if objectFrame is not None:
            hitResult = objectFrame.hitTestContent(objectPos)
            hitElement = hitResult.element()
        
        if not hitElement.isNull() and \
           hitElement.tagName().lower() in ["embed", "object"]:
            self.__element = hitElement
            return
        
        # hit test failed, trying to find element by src
        # attribute in elements of all frames on page (although less accurate
        frames = []
        frames.append(view.page().mainFrame())
        while frames:
            frame = frames.pop(0)
            if not frame:
                continue
            docElement = frame.documentElement()
            elements = QWebElementCollection()
            elements.append(docElement.findAll("embed"))
            elements.append(docElement.findAll("object"))
            
            for element in elements:
                if not self.__checkElement(element) and \
                   not self.__checkUrlOnElement(element, view):
                    continue
                self.__element = element
                return
            frames.extend(frame.childFrames())
    
    def __checkUrlOnElement(self, element, view):
        """
        Private slot to check the URL of an element.
        
        @param element reference to the element to check (QWebElement)
        @param view reference to the view object (QWebView)
        @return flag indicating a positive result (boolean)
        """
        checkString = element.attribute("src")
        if checkString == "":
            checkString = element.attribute("data")
        if checkString == "":
            checkString = element.attribute("value")
        
        checkString = view.url().resolved(QUrl(checkString)).toString(
            QUrl.RemoveQuery)
        return self.__url.toEncoded().contains(
            QByteArray(checkString.encode("utf-8")))
    
    def __checkElement(self, element):
        """
        Private slot to check an element against the saved arguments.
        
        @param element reference to the element to check (QWebElement)
        @return flag indicating a positive result (boolean)
        """
        if self.__argumentNames == element.attributeNames():
            for name in self.__argumentNames:
                if element.attribute(name) not in self.__argumentValues:
                    return False
            
            return True
        
        return False
    
    def __hideAdBlocked(self):
        """
        Private slot to hide the object.
        """
        self.__findElement()
        if not self.__element.isNull():
            self.__element.setStyleProperty("display", "none")
        else:
            self.hide()
    
    def __showInfo(self):
        """
        Private slot to show information about the blocked object.
        """
        dlg = QDialog()
        dlg.setWindowTitle(self.tr("Flash Object"))
        dlg.setSizeGripEnabled(True)
        layout = QFormLayout(dlg)
        layout.addRow(QLabel(self.tr("<b>Attribute Name</b>")),
                      QLabel(self.tr("<b>Value</b>")))
        
        index = 0
        for name in self.__argumentNames:
            nameLabel = QLabel(self.__elide(name, length=30))
            value = self.__argumentValues[index]
            valueLabel = QLabel(self.__elide(value, length=60))
            valueLabel.setTextInteractionFlags(
                Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse)
            layout.addRow(nameLabel, valueLabel)
            
            index += 1
        
        if index == 0:
            layout.addRow(QLabel(self.tr("No information available.")))
        
        dlg.setMaximumHeight(500)
        dlg.setMaximumWidth(500)
        dlg.exec_()
    
    def __elide(self, txt, mode=Qt.ElideMiddle, length=40):
        """
        Private method to elide some text.
        
        @param txt text to be elided (string)
        @keyparam mode elide mode (Qt.TextElideMode)
        @keyparam length amount of characters to be used (integer)
        @return the elided text (string)
        """
        if mode == Qt.ElideNone or len(txt) < length:
            return txt
        elif mode == Qt.ElideLeft:
            return "...{0}".format(txt[-length:])
        elif mode == Qt.ElideMiddle:
            return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):])
        elif mode == Qt.ElideRight:
            return "{0}...".format(txt[:length])
        else:
            # just in case
            return txt
    
    @classmethod
    def isAlreadyAccepted(cls, url, argumentNames, argumentValues):
        """
        Class method to check, if the given parameter combination is being
        accepted.
        
        @param url URL to be checked for (QUrl)
        @param argumentNames argument names to be checked for (list of strings)
        @param argumentValues argument values to be checked for (list of
            strings)
        @return flag indicating that this was already accepted (boolean)
        """
        return url == cls._acceptedUrl and \
            argumentNames == cls._acceptedArgNames and \
            argumentValues == cls._acceptedArgValues
Exemple #21
0
    def url_watcher(self, url: QtCore.QUrl):
        """Watches the QWebEngineView for url changes."""
        if url.host() not in [
                'twitch.tv', 'localhost',
                self.ENDPOINT.host(), 'passport.twitch.tv'
        ] and url.host() != '':

            self.LOGGER.warning(
                f'Redirected to an unsupported host!  ({url.host()})')

            self.LOGGER.debug('Creating informative dialog...')
            _m = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Critical, 'Token Generator',
                f'You were redirected to an unsupported host.  ({url.host()})',
                QtWidgets.QMessageBox.Ok, self)

            self.LOGGER.debug('Displaying dialog...')
            self.hide()
            _m.exec()

            self.LOGGER.debug('Ensuring dialog is deleted properly...')
            if not sip.isdeleted(_m):
                _m.deleteLater()

            self.browser.setUrl(QtCore.QUrl('about:blank'))
            return self.reject()

        if url.hasFragment():
            query = QtCore.QUrlQuery(url.fragment())

        else:
            query = QtCore.QUrlQuery(url.query())

        if not query.hasQueryItem('state') and url.path() != '/two_factor/new':
            self.LOGGER.warning('No state sent!')

            self.LOGGER.debug('Creating informative dialog...')
            _m = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical,
                                       'Token Generator',
                                       "The state parameter wasn't passed.",
                                       QtWidgets.QMessageBox.Ok, self)

            self.LOGGER.debug('Displaying dialog...')
            self.hide()
            _m.exec()

            self.LOGGER.debug('Ensuring dialog is deleted properly...')
            if not sip.isdeleted(_m):
                _m.deleteLater()

            self.browser.setUrl(QtCore.QUrl('about:blank'))
            return self.reject()

        if query.hasQueryItem(
                'state') and self._state != query.queryItemValue('state'):
            self.LOGGER.warning('Sent state is not our state!')

            self.LOGGER.debug('Creating informative dialog...')
            _m = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical,
                                       'Token Generator',
                                       'Sent state is not our state.',
                                       QtWidgets.QMessageBox.Ok, self)

            self.LOGGER.debug('Displaying dialog...')
            _m.exec()
            self.hide()

            self.LOGGER.debug('Ensuring dialog is deleted properly...')
            if not sip.isdeleted(_m):
                _m.deleteLater()

            self.browser.setUrl(QtCore.QUrl('about:blank'))
            return self.reject()

        if query.hasQueryItem('access_token'):
            # Declarations
            app: QtWidgets.QApplication = QtWidgets.QApplication.instance()
            client_id = app.client.settings['extensions']['twitch'][
                'client_id'].value
            scopes: typing.List[Scopes] = [
                Scopes(s) for s in parse.unquote(query.queryItemValue(
                    'scope')).split('+')
            ]

            self.GENERATED.emit(
                token.Token(client_id, query.queryItemValue('access_token'),
                            scopes))
            self.accept()
Exemple #22
0
class E5SslInfoWidget(QMenu):
    """
    Class implementing a widget to show SSL certificate infos.
    """
    def __init__(self, url, configuration, parent=None):
        """
        Constructor
        
        @param url URL to show SSL info for (QUrl)
        @param configuration SSL configuration (QSslConfiguration)
        @param parent reference to the parent widget (QWidget)
        """
        super(E5SslInfoWidget, self).__init__(parent)
        
        self.__url = QUrl(url)
        self.__configuration = QSslConfiguration(configuration)
        
        self.setMinimumWidth(400)
        
        certList = self.__configuration.peerCertificateChain()
        if certList:
            cert = certList[0]
        else:
            cert = QSslCertificate()
        
        layout = QGridLayout(self)
        rows = 0
        
        ##########################################
        ## Identity Information
        ##########################################
        imageLabel = QLabel(self)
        layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter)
        
        label = QLabel(self)
        label.setWordWrap(True)
        label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        label.setText(self.tr("Identity"))
        font = label.font()
        font.setBold(True)
        label.setFont(font)
        layout.addWidget(label, rows, 1)
        rows += 1
        
        label = QLabel(self)
        label.setWordWrap(True)
        if cert.isNull():
            label.setText(self.tr(
                "Warning: this site is NOT carrying a certificate."))
            imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png"))
        else:
            if qVersion() >= "5.0.0":
                valid = not cert.isBlacklisted()
            else:
                valid = cert.isValid()
            if valid:
                if qVersion() >= "5.0.0":
                    txt = ", ".join(
                        cert.issuerInfo(QSslCertificate.CommonName))
                else:
                    txt = cert.issuerInfo(QSslCertificate.CommonName)
                label.setText(self.tr(
                    "The certificate for this site is valid"
                    " and has been verified by:\n{0}").format(
                    Utilities.decodeString(txt)))
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityHigh32.png"))
            else:
                label.setText(self.tr(
                    "The certificate for this site is NOT valid."))
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            layout.addWidget(label, rows, 1)
            rows += 1
            
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(
                '<a href="moresslinfos">' +
                self.tr("Certificate Information") + "</a>")
            label.linkActivated.connect(self.__showCertificateInfos)
            layout.addWidget(label, rows, 1)
            rows += 1
        
        ##########################################
        ## Identity Information
        ##########################################
        imageLabel = QLabel(self)
        layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter)
        
        label = QLabel(self)
        label.setWordWrap(True)
        label.setText(self.tr("Encryption"))
        font = label.font()
        font.setBold(True)
        label.setFont(font)
        layout.addWidget(label, rows, 1)
        rows += 1
        
        cipher = self.__configuration.sessionCipher()
        if cipher.isNull():
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(self.tr(
                'Your connection to "{0}" is NOT encrypted.\n').format(
                self.__url.host()))
            layout.addWidget(label, rows, 1)
            imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png"))
            rows += 1
        else:
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(self.tr(
                'Your connection to "{0}" is encrypted.').format(
                self.__url.host()))
            layout.addWidget(label, rows, 1)
            
            proto = cipher.protocol()
            if proto == QSsl.SslV3:
                sslVersion = "SSL 3.0"
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            elif proto == QSsl.TlsV1SslV3:
                sslVersion = "TLS 1.0/SSL 3.0"
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            elif proto == QSsl.SslV2:
                sslVersion = "SSL 2.0"
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            else:
                sslVersion = self.tr("unknown")
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            if qVersion() >= "5.0.0":
                if proto == QSsl.TlsV1_0:
                    sslVersion = "TLS 1.0"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
                elif proto == QSsl.TlsV1_1:
                    sslVersion = "TLS 1.1"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
                elif proto == QSsl.TlsV1_2:
                    sslVersion = "TLS 1.2"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
            else:
                if proto == QSsl.TlsV1:
                    sslVersion = "TLS 1.0"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
            rows += 1
            
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(self.tr(
                "It uses protocol: {0}").format(sslVersion))
            layout.addWidget(label, rows, 1)
            rows += 1
            
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(self.tr(
                "It is encrypted using {0} at {1} bits, "
                "with {2} for message authentication and "
                "{3} as key exchange mechanism.\n\n").format(
                cipher.encryptionMethod(),
                cipher.usedBits(),
                cipher.authenticationMethod(),
                cipher.keyExchangeMethod()))
            layout.addWidget(label, rows, 1)
            rows += 1
    
    def showAt(self, pos):
        """
        Public method to show the widget.
        
        @param pos position to show at (QPoint)
        """
        self.adjustSize()
        xpos = pos.x() - self.width()
        if xpos < 0:
            xpos = 10
        p = QPoint(xpos, pos.y() + 10)
        self.move(p)
        self.show()
    
    def __showCertificateInfos(self):
        """
        Private slot to show certificate information.
        """
        from .E5SslCertificatesInfoDialog import E5SslCertificatesInfoDialog
        dlg = E5SslCertificatesInfoDialog(
            self.__configuration.peerCertificateChain())
        dlg.exec_()
    
    def accept(self):
        """
        Public method to accept the widget.
        """
        self.close()
Exemple #23
0
class CoverArtImage:

    # Indicate if types are provided by the source, ie. CAA or certain file
    # formats may have types associated with cover art, but some other sources
    # don't provide such information
    support_types = False
    # `is_front` has to be explicitly set, it is used to handle CAA is_front
    # indicator
    is_front = None
    sourceprefix = "URL"

    def __init__(self, url=None, types=None, comment='', data=None):
        if types is None:
            self.types = []
        else:
            self.types = types
        if url is not None:
            self.parse_url(url)
        else:
            self.url = None
        self.comment = comment
        self.datahash = None
        # thumbnail is used to link to another CoverArtImage, ie. for PDFs
        self.thumbnail = None
        self.can_be_saved_to_tags = True
        self.can_be_saved_to_disk = True
        self.can_be_saved_to_metadata = True
        if data is not None:
            self.set_data(data)

    def parse_url(self, url):
        self.url = QUrl(url)
        self.host = string_(self.url.host())
        self.port = self.url.port(80)
        self.path = string_(self.url.path(QUrl.FullyEncoded))
        if self.url.hasQuery():
            self.path += '?' + string_(self.url.query(QUrl.FullyEncoded))

    @property
    def source(self):
        if self.url is not None:
            return "%s: %s" % (self.sourceprefix, self.url.toString())
        else:
            return "%s" % self.sourceprefix

    def is_front_image(self):
        """Indicates if image is considered as a 'front' image.
        It depends on few things:
            - if `is_front` was set, it is used over anything else
            - if `types` was set, search for 'front' in it
            - if `support_types` is False, default to True for any image
            - if `support_types` is True, default to False for any image
        """
        if not self.can_be_saved_to_metadata:
            # ignore thumbnails
            return False
        if self.is_front is not None:
            return self.is_front
        if 'front' in self.types:
            return True
        return (self.support_types is False)

    def imageinfo_as_string(self):
        if self.datahash is None:
            return ""
        return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % (self.width,
                                                                self.height,
                                                                self.mimetype,
                                                                self.extension,
                                                                self.datalength,
                                                                self.tempfile_filename)

    def __repr__(self):
        p = []
        if self.url is not None:
            p.append("url=%r" % self.url.toString())
        if self.types:
            p.append("types=%r" % self.types)
        if self.is_front is not None:
            p.append("is_front=%r" % self.is_front)
        if self.comment:
            p.append("comment=%r" % self.comment)
        return "%s(%s)" % (self.__class__.__name__, ", ".join(p))

    def __str__(self):
        p = ['Image']
        if self.url is not None:
            p.append("from %s" % self.url.toString())
        if self.types:
            p.append("of type %s" % ','.join(self.types))
        if self.comment:
            p.append("and comment '%s'" % self.comment)
        return ' '.join(p)

    def __eq__(self, other):
        if self and other:
            if self.types and other.types:
                return (self.datahash, self.types) == (other.datahash, other.types)
            else:
                return self.datahash == other.datahash
        elif not self and not other:
            return True
        else:
            return False

    def __hash__(self):
        if self.datahash is None:
            return 0
        return hash(self.datahash.hash())

    def set_data(self, data):
        """Store image data in a file, if data already exists in such file
           it will be re-used and no file write occurs
        """
        if self.datahash:
            self.datahash.delete_file()
            self.datahash = None

        try:
            (self.width, self.height, self.mimetype, self.extension,
             self.datalength) = imageinfo.identify(data)
        except imageinfo.IdentificationError as e:
            raise CoverArtImageIdentificationError(e)

        try:
            self.datahash = DataHash(data, suffix=self.extension)
        except (OSError, IOError) as e:
            raise CoverArtImageIOError(e)

    @property
    def maintype(self):
        """Returns one type only, even for images having more than one type set.
        This is mostly used when saving cover art to tags because most formats
        don't support multiple types for one image.
        Images coming from CAA can have multiple types (ie. 'front, booklet').
        """
        if self.is_front_image() or not self.types or 'front' in self.types:
            return 'front'
        # TODO: do something better than randomly using the first in the list
        return self.types[0]

    def _make_image_filename(self, filename, dirname, metadata):
        filename = ScriptParser().eval(filename, metadata)
        if config.setting["ascii_filenames"]:
            if isinstance(filename, str):
                filename = unaccent(filename)
            filename = replace_non_ascii(filename)
        if not filename:
            filename = "cover"
        if not os.path.isabs(filename):
            filename = os.path.join(dirname, filename)
        # replace incompatible characters
        if config.setting["windows_compatibility"] or sys.platform == "win32":
            filename = replace_win32_incompat(filename)
        # remove null characters
        if isinstance(filename, bytes):
            filename = filename.replace(b"\x00", "")
        return encode_filename(filename)

    def save(self, dirname, metadata, counters):
        """Saves this image.

        :dirname: The name of the directory that contains the audio file
        :metadata: A metadata object
        :counters: A dictionary mapping filenames to the amount of how many
                    images with that filename were already saved in `dirname`.
        """
        if not self.can_be_saved_to_disk:
            return
        if (config.setting["caa_image_type_as_filename"] and
            not self.is_front_image()):
            filename = self.maintype
            log.debug("Make cover filename from types: %r -> %r",
                      self.types, filename)
        else:
            filename = config.setting["cover_image_filename"]
            log.debug("Using default cover image filename %r", filename)
        filename = self._make_image_filename(filename, dirname, metadata)

        overwrite = config.setting["save_images_overwrite"]
        ext = encode_filename(self.extension)
        image_filename = self._next_filename(filename, counters)
        while os.path.exists(image_filename + ext) and not overwrite:
            if not self._is_write_needed(image_filename + ext):
                break
            image_filename = self._next_filename(filename, counters)
        else:
            new_filename = image_filename + ext
            # Even if overwrite is enabled we don't need to write the same
            # image multiple times
            if not self._is_write_needed(new_filename):
                return
            log.debug("Saving cover image to %r", new_filename)
            try:
                new_dirname = os.path.dirname(new_filename)
                if not os.path.isdir(new_dirname):
                    os.makedirs(new_dirname)
                shutil.copyfile(self.tempfile_filename, new_filename)
            except (OSError, IOError) as e:
                raise CoverArtImageIOError(e)

    def _next_filename(self, filename, counters):
        if counters[filename]:
            new_filename = b"%b (%d)" % (filename, counters[filename])
        else:
            new_filename = filename
        counters[filename] += 1
        return new_filename

    def _is_write_needed(self, filename):
        if (os.path.exists(filename)
                and os.path.getsize(filename) == self.datalength):
            log.debug("Identical file size, not saving %r", filename)
            return False
        return True

    @property
    def data(self):
        """Reads the data from the temporary file created for this image.
        May raise CoverArtImageIOError
        """
        try:
            return self.datahash.data
        except (OSError, IOError) as e:
            raise CoverArtImageIOError(e)

    @property
    def tempfile_filename(self):
        return self.datahash.filename

    def types_as_string(self, translate=True, separator=', '):
        if self.types:
            types = self.types
        elif self.is_front_image():
            types = ['front']
        else:
            types = ['-']
        if translate:
            types = [translate_caa_type(type) for type in types]
        return separator.join(types)
    def queryProxy(self, query):
        """
        Public method to determine a proxy for a given query.
        
        @param query reference to the query object (QNetworkProxyQuery)
        @return list of proxies in order of preference (list of QNetworkProxy)
        """
        if query.queryType() == QNetworkProxyQuery.UrlRequest and query.protocolTag() in ["http", "https", "ftp"]:
            # use proxy at all ?
            if not Preferences.getUI("UseProxy"):
                return [QNetworkProxy(QNetworkProxy.NoProxy)]

            # test for exceptions
            exceptions = Preferences.getUI("ProxyExceptions")
            if exceptions != self.__exceptions:
                self.__setExceptions(exceptions)
            urlHost = query.url().host()
            for matcher in self.__hostnameMatchers:
                if matcher.match(urlHost):
                    return [QNetworkProxy(QNetworkProxy.NoProxy)]

            # determine proxy
            if Preferences.getUI("UseSystemProxy"):
                proxyList = QNetworkProxyFactory.systemProxyForQuery(query)
                if (
                    not Globals.isWindowsPlatform()
                    and len(proxyList) == 1
                    and proxyList[0].type() == QNetworkProxy.NoProxy
                ):
                    # try it the Python way
                    # scan the environment for variables named <scheme>_proxy
                    # scan over whole environment to make this case insensitive
                    for name, value in os.environ.items():
                        name = name.lower()
                        if value and name[-6:] == "_proxy" and name[:-6] == query.protocolTag().lower():
                            url = QUrl(value)
                            if url.scheme() == "http":
                                proxyType = QNetworkProxy.HttpProxy
                            elif url.scheme() == "https":
                                proxyType = QNetworkProxy.HttpCachingProxy
                            elif url.scheme() == "ftp":
                                proxyType = QNetworkProxy.FtpCachingProxy
                            else:
                                proxyType = QNetworkProxy.HttpProxy
                            proxy = QNetworkProxy(proxyType, url.host(), url.port(), url.userName(), url.password())
                            proxyList = [proxy]
                            break
                if proxyList:
                    scheme = schemeFromProxyType(proxyList[0].type())
                    if scheme == "":
                        scheme = "Http"
                    if scheme != "NoProxy":
                        proxyList[0].setUser(Preferences.getUI("ProxyUser/{0}".format(scheme)))
                        proxyList[0].setPassword(Preferences.getUI("ProxyPassword/{0}".format(scheme)))
                    return proxyList
                else:
                    return [QNetworkProxy(QNetworkProxy.NoProxy)]
            else:
                if Preferences.getUI("UseHttpProxyForAll"):
                    protocolKey = "Http"
                else:
                    protocolKey = query.protocolTag().capitalize()
                host = Preferences.getUI("ProxyHost/{0}".format(protocolKey))
                if not host:
                    E5MessageBox.critical(
                        None,
                        QCoreApplication.translate("E5NetworkProxyFactory", "Proxy Configuration Error"),
                        QCoreApplication.translate(
                            "E5NetworkProxyFactory",
                            """Proxy usage was activated"""
                            """ but no proxy host for protocol"""
                            """ '{0}' configured.""",
                        ).format(protocolKey),
                    )
                    return [QNetworkProxy(QNetworkProxy.DefaultProxy)]
                else:
                    if protocolKey in ["Http", "Https", "Ftp"]:
                        if query.protocolTag() == "ftp":
                            proxyType = QNetworkProxy.FtpCachingProxy
                        elif query.protocolTag() == "https":
                            proxyType = QNetworkProxy.HttpCachingProxy
                        else:
                            proxyType = QNetworkProxy.HttpProxy
                        proxy = QNetworkProxy(
                            proxyType,
                            host,
                            Preferences.getUI("ProxyPort/" + protocolKey),
                            Preferences.getUI("ProxyUser/" + protocolKey),
                            Preferences.getUI("ProxyPassword/" + protocolKey),
                        )
                    else:
                        proxy = QNetworkProxy(QNetworkProxy.DefaultProxy)
                    return [proxy, QNetworkProxy(QNetworkProxy.DefaultProxy)]
        else:
            return [QNetworkProxy(QNetworkProxy.NoProxy)]
Exemple #25
0
class PageThumbnailer(QObject):
    """
    Class implementing a thumbnail creator for web sites.
    
    @signal thumbnailCreated(QPixmap) emitted after the thumbnail has been
        created
    """
    thumbnailCreated = pyqtSignal(QPixmap)

    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent object (QObject)
        """
        super(PageThumbnailer, self).__init__(parent)

        self.__size = QSize(231, 130)
        self.__loadTitle = False
        self.__title = ""
        self.__url = QUrl()

        self.__view = QWebEngineView()
        self.__view.setAttribute(Qt.WA_DontShowOnScreen)
        self.__view.resize(1920, 1080)
        self.__view.show()

    def setSize(self, size):
        """
        Public method to set the size of the image.
        
        @param size size of the image (QSize)
        """
        if size.isValid():
            self.__size = QSize(size)

    def setUrl(self, url):
        """
        Public method to set the URL of the site to be thumbnailed.
        
        @param url URL of the web site (QUrl)
        """
        if url.isValid():
            self.__url = QUrl(url)

    def url(self):
        """
        Public method to get the URL of the thumbnail.
        
        @return URL of the thumbnail (QUrl)
        """
        return QUrl(self.__url)

    def loadTitle(self):
        """
        Public method to check, if the title is loaded from the web site.
        
        @return flag indicating, that the title is loaded (boolean)
        """
        return self.__loadTitle

    def setLoadTitle(self, load):
        """
        Public method to set a flag indicating to load the title from
        the web site.
        
        @param load flag indicating to load the title (boolean)
        """
        self.__loadTitle = load

    def title(self):
        """
        Public method to get the title of the thumbnail.
        
        @return title of the thumbnail (string)
        """
        if self.__title:
            title = self.__title
        else:
            title = self.__url.host()
        if not title:
            title = self.__url.toString()
        return title

    def start(self):
        """
        Public method to start the thumbnailing action.
        """
        self.__view.loadFinished.connect(self.__createThumbnail)
        self.__view.load(self.__url)

    def __createThumbnail(self, status):
        """
        Private slot creating the thumbnail of the web site.
        
        @param status flag indicating a successful load of the web site
            (boolean)
        """
        if not status:
            self.thumbnailCreated.emit(QPixmap())
            return

        QTimer.singleShot(1000, self.__grabThumbnail)

    def __grabThumbnail(self):
        """
        Private slot to grab the thumbnail image from the view.
        """
        self.__title = self.__view.title()

        image = QImage(self.__view.size(), QImage.Format_ARGB32)
        painter = QPainter(image)
        self.__view.render(painter)
        painter.end()

        scaledImage = image.scaled(self.__size, Qt.KeepAspectRatioByExpanding,
                                   Qt.SmoothTransformation)

        self.thumbnailCreated.emit(QPixmap.fromImage(scaledImage))
Exemple #26
0
class CoverArtImage:

    # Indicate if types are provided by the source, ie. CAA or certain file
    # formats may have types associated with cover art, but some other sources
    # don't provide such information
    support_types = False
    # Indicates that the source supports multiple types per image.
    support_multi_types = False
    # `is_front` has to be explicitly set, it is used to handle CAA is_front
    # indicator
    is_front = None
    sourceprefix = "URL"

    def __init__(self,
                 url=None,
                 types=None,
                 comment='',
                 data=None,
                 support_types=None,
                 support_multi_types=None,
                 id3_type=None):
        if types is None:
            self.types = []
        else:
            self.types = types
        if url is not None:
            self.parse_url(url)
        else:
            self.url = None
        self.comment = comment
        self.datahash = None
        # thumbnail is used to link to another CoverArtImage, ie. for PDFs
        self.thumbnail = None
        self.can_be_saved_to_tags = True
        self.can_be_saved_to_disk = True
        self.can_be_saved_to_metadata = True
        self.id3_type = id3_type
        if support_types is not None:
            self.support_types = support_types
        if support_multi_types is not None:
            self.support_multi_types = support_multi_types
        if data is not None:
            self.set_data(data)

    def parse_url(self, url):
        self.url = QUrl(url)
        self.host = self.url.host()
        self.port = self.url.port(443 if self.url.scheme() == 'https' else 80)
        self.path = self.url.path(QUrl.ComponentFormattingOption.FullyEncoded)
        if self.url.hasQuery():
            query = QUrlQuery(self.url.query())
            self.queryargs = dict(query.queryItems())
        else:
            self.queryargs = None

    @property
    def source(self):
        if self.url is not None:
            return "%s: %s" % (self.sourceprefix, self.url.toString())
        else:
            return "%s" % self.sourceprefix

    def is_front_image(self):
        """Indicates if image is considered as a 'front' image.
        It depends on few things:
            - if `is_front` was set, it is used over anything else
            - if `types` was set, search for 'front' in it
            - if `support_types` is False, default to True for any image
            - if `support_types` is True, default to False for any image
        """
        if not self.can_be_saved_to_metadata:
            # ignore thumbnails
            return False
        if self.is_front is not None:
            return self.is_front
        if 'front' in self.types:
            return True
        return (self.support_types is False)

    def imageinfo_as_string(self):
        if self.datahash is None:
            return ""
        return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % (
            self.width, self.height, self.mimetype, self.extension,
            self.datalength, self.tempfile_filename)

    def __repr__(self):
        p = []
        if self.url is not None:
            p.append("url=%r" % self.url.toString())
        if self.types:
            p.append("types=%r" % self.types)
        p.append('support_types=%r' % self.support_types)
        p.append('support_multi_types=%r' % self.support_types)
        if self.is_front is not None:
            p.append("is_front=%r" % self.is_front)
        if self.comment:
            p.append("comment=%r" % self.comment)
        return "%s(%s)" % (self.__class__.__name__, ", ".join(p))

    def __str__(self):
        p = ['Image']
        if self.url is not None:
            p.append("from %s" % self.url.toString())
        if self.types:
            p.append("of type %s" % ','.join(self.types))
        if self.comment:
            p.append("and comment '%s'" % self.comment)
        return ' '.join(p)

    def __eq__(self, other):
        if self and other:
            if self.support_types and other.support_types:
                if self.support_multi_types and other.support_multi_types:
                    return (self.datahash, self.types) == (other.datahash,
                                                           other.types)
                else:
                    return (self.datahash, self.maintype) == (other.datahash,
                                                              other.maintype)
            else:
                return self.datahash == other.datahash
        return not self and not other

    def __hash__(self):
        if self.datahash is None:
            return 0
        return hash(self.datahash.hash())

    def set_data(self, data):
        """Store image data in a file, if data already exists in such file
           it will be re-used and no file write occurs
        """
        if self.datahash:
            self.datahash.delete_file()
            self.datahash = None

        try:
            (self.width, self.height, self.mimetype, self.extension,
             self.datalength) = imageinfo.identify(data)
        except imageinfo.IdentificationError as e:
            raise CoverArtImageIdentificationError(e)

        try:
            self.datahash = DataHash(data, suffix=self.extension)
        except OSError as e:
            raise CoverArtImageIOError(e)

    @property
    def maintype(self):
        """Returns one type only, even for images having more than one type set.
        This is mostly used when saving cover art to tags because most formats
        don't support multiple types for one image.
        Images coming from CAA can have multiple types (ie. 'front, booklet').
        """
        if self.is_front_image() or not self.types or 'front' in self.types:
            return 'front'
        # TODO: do something better than randomly using the first in the list
        return self.types[0]

    @property
    def id3_type(self):
        """Returns the ID3 APIC image type.
        If explicitly set the type is returned as is, otherwise it is derived
        from `maintype`. See http://www.id3.org/id3v2.4.0-frames"""
        if self._id3_type is not None:
            return self._id3_type
        else:
            return image_type_as_id3_num(self.maintype)

    @id3_type.setter
    def id3_type(self, type):
        """Explicitly sets the ID3 APIC image type.
        If set to None the type will be derived from `maintype`."""
        if type is not None:
            type = Id3ImageType(type)
        self._id3_type = type

    def _make_image_filename(self, filename, dirname, _metadata):
        metadata = Metadata()
        metadata.copy(_metadata)
        metadata["coverart_maintype"] = self.maintype
        metadata["coverart_comment"] = self.comment
        if self.is_front:
            metadata.add_unique("coverart_types", "front")
        for cover_type in self.types:
            metadata.add_unique("coverart_types", cover_type)
        filename = script_to_filename(filename, metadata)
        if not filename:
            filename = DEFAULT_COVER_IMAGE_FILENAME
        if not is_absolute_path(filename):
            filename = os.path.join(dirname, filename)
        return encode_filename(filename)

    def save(self, dirname, metadata, counters):
        """Saves this image.

        :dirname: The name of the directory that contains the audio file
        :metadata: A metadata object
        :counters: A dictionary mapping filenames to the amount of how many
                    images with that filename were already saved in `dirname`.
        """
        if not self.can_be_saved_to_disk:
            return
        config = get_config()
        if config.setting[
                "image_type_as_filename"] and not self.is_front_image():
            filename = self.maintype
            log.debug("Make cover filename from types: %r -> %r", self.types,
                      filename)
        else:
            filename = config.setting["cover_image_filename"]
            log.debug("Using default cover image filename %r", filename)
        filename = self._make_image_filename(filename, dirname, metadata)

        overwrite = config.setting["save_images_overwrite"]
        ext = encode_filename(self.extension)
        image_filename = self._next_filename(filename, counters)
        while os.path.exists(image_filename + ext) and not overwrite:
            if not self._is_write_needed(image_filename + ext):
                break
            image_filename = self._next_filename(filename, counters)
        else:
            new_filename = image_filename + ext
            # Even if overwrite is enabled we don't need to write the same
            # image multiple times
            if not self._is_write_needed(new_filename):
                return
            log.debug("Saving cover image to %r", new_filename)
            try:
                new_dirname = os.path.dirname(new_filename)
                if not os.path.isdir(new_dirname):
                    os.makedirs(new_dirname)
                shutil.copyfile(self.tempfile_filename, new_filename)
            except OSError as e:
                raise CoverArtImageIOError(e)

    @staticmethod
    def _next_filename(filename, counters):
        if counters[filename]:
            new_filename = "%s (%d)" % (decode_filename(filename),
                                        counters[filename])
        else:
            new_filename = filename
        counters[filename] += 1
        return encode_filename(new_filename)

    def _is_write_needed(self, filename):
        if (os.path.exists(filename)
                and os.path.getsize(filename) == self.datalength):
            log.debug("Identical file size, not saving %r", filename)
            return False
        return True

    @property
    def data(self):
        """Reads the data from the temporary file created for this image.
        May raise CoverArtImageIOError
        """
        try:
            return self.datahash.data
        except OSError as e:
            raise CoverArtImageIOError(e)

    @property
    def tempfile_filename(self):
        return self.datahash.filename

    def normalized_types(self):
        if self.types:
            types = sorted(set(self.types))
        elif self.is_front_image():
            types = ['front']
        else:
            types = ['-']
        return types

    def types_as_string(self, translate=True, separator=', '):
        types = self.normalized_types()
        if translate:
            types = [translate_caa_type(type) for type in types]
        return separator.join(types)
Exemple #27
0
class HttpWindow(QDialog):
    def __init__(self, parent=None):
        super(HttpWindow, self).__init__(parent)

        self.url = QUrl()
        self.qnam = QNetworkAccessManager()
        self.reply = None
        self.outFile = None
        self.httpGetId = 0
        self.httpRequestAborted = False

        self.urlLineEdit = QLineEdit('https://www.qt.io')

        urlLabel = QLabel("&URL:")
        urlLabel.setBuddy(self.urlLineEdit)
        self.statusLabel = QLabel(
                "Please enter the URL of a file you want to download.")
        self.statusLabel.setWordWrap(True)

        self.downloadButton = QPushButton("Download")
        self.downloadButton.setDefault(True)
        self.quitButton = QPushButton("Quit")
        self.quitButton.setAutoDefault(False)

        buttonBox = QDialogButtonBox()
        buttonBox.addButton(self.downloadButton, QDialogButtonBox.ActionRole)
        buttonBox.addButton(self.quitButton, QDialogButtonBox.RejectRole)

        self.progressDialog = QProgressDialog(self)

        self.urlLineEdit.textChanged.connect(self.enableDownloadButton)
        self.qnam.authenticationRequired.connect(
                self.slotAuthenticationRequired)
        self.qnam.sslErrors.connect(self.sslErrors)
        self.progressDialog.canceled.connect(self.cancelDownload)
        self.downloadButton.clicked.connect(self.downloadFile)
        self.quitButton.clicked.connect(self.close)

        topLayout = QHBoxLayout()
        topLayout.addWidget(urlLabel)
        topLayout.addWidget(self.urlLineEdit)

        mainLayout = QVBoxLayout()
        mainLayout.addLayout(topLayout)
        mainLayout.addWidget(self.statusLabel)
        mainLayout.addWidget(buttonBox)
        self.setLayout(mainLayout)

        self.setWindowTitle("HTTP")
        self.urlLineEdit.setFocus()

    def startRequest(self, url):
        self.reply = self.qnam.get(QNetworkRequest(url))
        self.reply.finished.connect(self.httpFinished)
        self.reply.readyRead.connect(self.httpReadyRead)
        self.reply.downloadProgress.connect(self.updateDataReadProgress)

    def downloadFile(self):
        self.url = QUrl(self.urlLineEdit.text())
        fileInfo = QFileInfo(self.url.path())
        fileName = fileInfo.fileName()

        if not fileName:
            fileName = 'index.html'

        if QFile.exists(fileName):
            ret = QMessageBox.question(self, "HTTP",
                    "There already exists a file called %s in the current "
                    "directory. Overwrite?" % fileName,
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

            if ret == QMessageBox.No:
                return

            QFile.remove(fileName)

        self.outFile = QFile(fileName)
        if not self.outFile.open(QIODevice.WriteOnly):
            QMessageBox.information(self, "HTTP",
                    "Unable to save the file %s: %s." % (fileName, self.outFile.errorString()))
            self.outFile = None
            return

        self.progressDialog.setWindowTitle("HTTP")
        self.progressDialog.setLabelText("Downloading %s." % fileName)
        self.downloadButton.setEnabled(False)

        self.httpRequestAborted = False
        self.startRequest(self.url)

    def cancelDownload(self):
        self.statusLabel.setText("Download canceled.")
        self.httpRequestAborted = True
        if self.reply is not None:
            self.reply.abort()
        self.downloadButton.setEnabled(True)

    def httpFinished(self):
        if self.httpRequestAborted:
            if self.outFile is not None:
                self.outFile.close()
                self.outFile.remove()
                self.outFile = None

            self.reply.deleteLater()
            self.reply = None
            self.progressDialog.hide()
            return

        self.progressDialog.hide()
        self.outFile.flush()
        self.outFile.close()

        redirectionTarget = self.reply.attribute(QNetworkRequest.RedirectionTargetAttribute)

        if self.reply.error():
            self.outFile.remove()
            QMessageBox.information(self, "HTTP",
                    "Download failed: %s." % self.reply.errorString())
            self.downloadButton.setEnabled(True)
        elif redirectionTarget is not None:
            newUrl = self.url.resolved(redirectionTarget)

            ret = QMessageBox.question(self, "HTTP",
                    "Redirect to %s?" % newUrl.toString(),
                    QMessageBox.Yes | QMessageBox.No)

            if ret == QMessageBox.Yes:
                self.url = newUrl
                self.reply.deleteLater()
                self.reply = None
                self.outFile.open(QIODevice.WriteOnly)
                self.outFile.resize(0)
                self.startRequest(self.url)
                return
        else:
            fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName()
            self.statusLabel.setText("Downloaded %s to %s." % (fileName, QDir.currentPath()))

            self.downloadButton.setEnabled(True)

        self.reply.deleteLater()
        self.reply = None
        self.outFile = None

    def httpReadyRead(self):
        if self.outFile is not None:
            self.outFile.write(self.reply.readAll())

    def updateDataReadProgress(self, bytesRead, totalBytes):
        if self.httpRequestAborted:
            return

        self.progressDialog.setMaximum(totalBytes)
        self.progressDialog.setValue(bytesRead)

    def enableDownloadButton(self):
        self.downloadButton.setEnabled(self.urlLineEdit.text() != '')

    def slotAuthenticationRequired(self, authenticator):
        import os
        from PyQt5 import uic

        ui = os.path.join(os.path.dirname(__file__), 'authenticationdialog.ui')
        dlg = uic.loadUi(ui)
        dlg.adjustSize()
        dlg.siteDescription.setText("%s at %s" % (authenticator.realm(), self.url.host()))

        dlg.userEdit.setText(self.url.userName())
        dlg.passwordEdit.setText(self.url.password())

        if dlg.exec_() == QDialog.Accepted:
            authenticator.setUser(dlg.userEdit.text())
            authenticator.setPassword(dlg.passwordEdit.text())

    def sslErrors(self, reply, errors):
        errorString = ", ".join([str(error.errorString()) for error in errors])

        ret = QMessageBox.warning(self, "HTTP Example",
                "One or more SSL errors has occurred: %s" % errorString,
                QMessageBox.Ignore | QMessageBox.Abort)

        if ret == QMessageBox.Ignore:
            self.reply.ignoreSslErrors()
Exemple #28
0
class CoverArtImage:

    # Indicate if types are provided by the source, ie. CAA or certain file
    # formats may have types associated with cover art, but some other sources
    # don't provide such information
    support_types = False
    # `is_front` has to be explicitly set, it is used to handle CAA is_front
    # indicator
    is_front = None
    sourceprefix = "URL"

    def __init__(self, url=None, types=None, comment='', data=None):
        if types is None:
            self.types = []
        else:
            self.types = types
        if url is not None:
            self.parse_url(url)
        else:
            self.url = None
        self.comment = comment
        self.datahash = None
        # thumbnail is used to link to another CoverArtImage, ie. for PDFs
        self.thumbnail = None
        self.can_be_saved_to_tags = True
        self.can_be_saved_to_disk = True
        self.can_be_saved_to_metadata = True
        if data is not None:
            self.set_data(data)

    def parse_url(self, url):
        self.url = QUrl(url)
        self.host = string_(self.url.host())
        self.port = self.url.port(80)
        self.path = string_(self.url.path(QUrl.FullyEncoded))
        if self.url.hasQuery():
            self.path += '?' + string_(self.url.query(QUrl.FullyEncoded))

    @property
    def source(self):
        if self.url is not None:
            return "%s: %s" % (self.sourceprefix, self.url.toString())
        else:
            return "%s" % self.sourceprefix

    def is_front_image(self):
        """Indicates if image is considered as a 'front' image.
        It depends on few things:
            - if `is_front` was set, it is used over anything else
            - if `types` was set, search for 'front' in it
            - if `support_types` is False, default to True for any image
            - if `support_types` is True, default to False for any image
        """
        if not self.can_be_saved_to_metadata:
            # ignore thumbnails
            return False
        if self.is_front is not None:
            return self.is_front
        if 'front' in self.types:
            return True
        return (self.support_types is False)

    def imageinfo_as_string(self):
        if self.datahash is None:
            return ""
        return "w=%d h=%d mime=%s ext=%s datalen=%d file=%s" % (
            self.width, self.height, self.mimetype, self.extension,
            self.datalength, self.tempfile_filename)

    def __repr__(self):
        p = []
        if self.url is not None:
            p.append("url=%r" % self.url.toString())
        if self.types:
            p.append("types=%r" % self.types)
        if self.is_front is not None:
            p.append("is_front=%r" % self.is_front)
        if self.comment:
            p.append("comment=%r" % self.comment)
        return "%s(%s)" % (self.__class__.__name__, ", ".join(p))

    def __str__(self):
        p = ['Image']
        if self.url is not None:
            p.append("from %s" % self.url.toString())
        if self.types:
            p.append("of type %s" % ','.join(self.types))
        if self.comment:
            p.append("and comment '%s'" % self.comment)
        return ' '.join(p)

    def __eq__(self, other):
        if self and other:
            if self.types and other.types:
                return (self.datahash, self.types) == (other.datahash,
                                                       other.types)
            else:
                return self.datahash == other.datahash
        elif not self and not other:
            return True
        else:
            return False

    def __hash__(self):
        if self.datahash is None:
            return 0
        return hash(self.datahash.hash())

    def set_data(self, data):
        """Store image data in a file, if data already exists in such file
           it will be re-used and no file write occurs
        """
        if self.datahash:
            self.datahash.delete_file()
            self.datahash = None

        try:
            (self.width, self.height, self.mimetype, self.extension,
             self.datalength) = imageinfo.identify(data)
        except imageinfo.IdentificationError as e:
            raise CoverArtImageIdentificationError(e)

        try:
            self.datahash = DataHash(data, suffix=self.extension)
        except (OSError, IOError) as e:
            raise CoverArtImageIOError(e)

    @property
    def maintype(self):
        """Returns one type only, even for images having more than one type set.
        This is mostly used when saving cover art to tags because most formats
        don't support multiple types for one image.
        Images coming from CAA can have multiple types (ie. 'front, booklet').
        """
        if self.is_front_image() or not self.types or 'front' in self.types:
            return 'front'
        # TODO: do something better than randomly using the first in the list
        return self.types[0]

    def _make_image_filename(self, filename, dirname, metadata):
        filename = ScriptParser().eval(filename, metadata)
        if config.setting["ascii_filenames"]:
            if isinstance(filename, str):
                filename = unaccent(filename)
            filename = replace_non_ascii(filename)
        if not filename:
            filename = "cover"
        if not os.path.isabs(filename):
            filename = os.path.join(dirname, filename)
        # replace incompatible characters
        if config.setting["windows_compatibility"] or sys.platform == "win32":
            filename = replace_win32_incompat(filename)
        # remove null characters
        if isinstance(filename, bytes):
            filename = filename.replace(b"\x00", "")
        return encode_filename(filename)

    def save(self, dirname, metadata, counters):
        """Saves this image.

        :dirname: The name of the directory that contains the audio file
        :metadata: A metadata object
        :counters: A dictionary mapping filenames to the amount of how many
                    images with that filename were already saved in `dirname`.
        """
        if not self.can_be_saved_to_disk:
            return
        if (config.setting["caa_image_type_as_filename"]
                and not self.is_front_image()):
            filename = self.maintype
            log.debug("Make cover filename from types: %r -> %r", self.types,
                      filename)
        else:
            filename = config.setting["cover_image_filename"]
            log.debug("Using default cover image filename %r", filename)
        filename = self._make_image_filename(filename, dirname, metadata)

        overwrite = config.setting["save_images_overwrite"]
        ext = encode_filename(self.extension)
        image_filename = self._next_filename(filename, counters)
        while os.path.exists(image_filename + ext) and not overwrite:
            if not self._is_write_needed(image_filename + ext):
                break
            image_filename = self._next_filename(filename, counters)
        else:
            new_filename = image_filename + ext
            # Even if overwrite is enabled we don't need to write the same
            # image multiple times
            if not self._is_write_needed(new_filename):
                return
            log.debug("Saving cover image to %r", new_filename)
            try:
                new_dirname = os.path.dirname(new_filename)
                if not os.path.isdir(new_dirname):
                    os.makedirs(new_dirname)
                shutil.copyfile(self.tempfile_filename, new_filename)
            except (OSError, IOError) as e:
                raise CoverArtImageIOError(e)

    def _next_filename(self, filename, counters):
        if counters[filename]:
            new_filename = b"%b (%d)" % (filename, counters[filename])
        else:
            new_filename = filename
        counters[filename] += 1
        return new_filename

    def _is_write_needed(self, filename):
        if (os.path.exists(filename)
                and os.path.getsize(filename) == self.datalength):
            log.debug("Identical file size, not saving %r", filename)
            return False
        return True

    @property
    def data(self):
        """Reads the data from the temporary file created for this image.
        May raise CoverArtImageIOError
        """
        try:
            return self.datahash.data
        except (OSError, IOError) as e:
            raise CoverArtImageIOError(e)

    @property
    def tempfile_filename(self):
        return self.datahash.filename

    def types_as_string(self, translate=True, separator=', '):
        if self.types:
            types = self.types
        elif self.is_front_image():
            types = ['front']
        else:
            types = ['-']
        if translate:
            types = [translate_caa_type(type) for type in types]
        return separator.join(types)
Exemple #29
0
class E5SslInfoWidget(QMenu):
    """
    Class implementing a widget to show SSL certificate infos.
    """
    def __init__(self, url, configuration, parent=None):
        """
        Constructor
        
        @param url URL to show SSL info for (QUrl)
        @param configuration SSL configuration (QSslConfiguration)
        @param parent reference to the parent widget (QWidget)
        """
        super(E5SslInfoWidget, self).__init__(parent)

        self.__url = QUrl(url)
        self.__configuration = QSslConfiguration(configuration)

        self.setMinimumWidth(400)

        certList = self.__configuration.peerCertificateChain()
        if certList:
            cert = certList[0]
        else:
            cert = QSslCertificate()

        layout = QGridLayout(self)
        rows = 0

        ##########################################
        ## Identity Information
        ##########################################
        imageLabel = QLabel(self)
        layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter)

        label = QLabel(self)
        label.setWordWrap(True)
        label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        label.setText(self.tr("Identity"))
        font = label.font()
        font.setBold(True)
        label.setFont(font)
        layout.addWidget(label, rows, 1)
        rows += 1

        label = QLabel(self)
        label.setWordWrap(True)
        if cert.isNull():
            label.setText(
                self.tr("Warning: this site is NOT carrying a certificate."))
            imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png"))
        else:
            if qVersion() >= "5.0.0":
                valid = not cert.isBlacklisted()
            else:
                valid = cert.isValid()
            if valid:
                if qVersion() >= "5.0.0":
                    txt = ", ".join(cert.issuerInfo(
                        QSslCertificate.CommonName))
                else:
                    txt = cert.issuerInfo(QSslCertificate.CommonName)
                label.setText(
                    self.tr("The certificate for this site is valid"
                            " and has been verified by:\n{0}").format(
                                Utilities.decodeString(txt)))
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityHigh32.png"))
            else:
                label.setText(
                    self.tr("The certificate for this site is NOT valid."))
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            layout.addWidget(label, rows, 1)
            rows += 1

            label = QLabel(self)
            label.setWordWrap(True)
            label.setText('<a href="moresslinfos">' +
                          self.tr("Certificate Information") + "</a>")
            label.linkActivated.connect(self.__showCertificateInfos)
            layout.addWidget(label, rows, 1)
            rows += 1

        ##########################################
        ## Identity Information
        ##########################################
        imageLabel = QLabel(self)
        layout.addWidget(imageLabel, rows, 0, Qt.AlignCenter)

        label = QLabel(self)
        label.setWordWrap(True)
        label.setText(self.tr("Encryption"))
        font = label.font()
        font.setBold(True)
        label.setFont(font)
        layout.addWidget(label, rows, 1)
        rows += 1

        cipher = self.__configuration.sessionCipher()
        if cipher.isNull():
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(
                self.tr('Your connection to "{0}" is NOT encrypted.\n').format(
                    self.__url.host()))
            layout.addWidget(label, rows, 1)
            imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32.png"))
            rows += 1
        else:
            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(
                self.tr('Your connection to "{0}" is encrypted.').format(
                    self.__url.host()))
            layout.addWidget(label, rows, 1)

            proto = cipher.protocol()
            if proto == QSsl.SslV3:
                sslVersion = "SSL 3.0"
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityHigh32.png"))
            elif proto == QSsl.TlsV1SslV3:
                sslVersion = "TLS 1.0/SSL 3.0"
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityHigh32.png"))
            elif proto == QSsl.SslV2:
                sslVersion = "SSL 2.0"
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            else:
                sslVersion = self.tr("unknown")
                imageLabel.setPixmap(
                    UI.PixmapCache.getPixmap("securityLow32.png"))
            if qVersion() >= "5.0.0":
                if proto == QSsl.TlsV1_0:
                    sslVersion = "TLS 1.0"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
                elif proto == QSsl.TlsV1_1:
                    sslVersion = "TLS 1.1"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
                elif proto == QSsl.TlsV1_2:
                    sslVersion = "TLS 1.2"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
            else:
                if proto == QSsl.TlsV1:
                    sslVersion = "TLS 1.0"
                    imageLabel.setPixmap(
                        UI.PixmapCache.getPixmap("securityHigh32.png"))
            rows += 1

            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(self.tr("It uses protocol: {0}").format(sslVersion))
            layout.addWidget(label, rows, 1)
            rows += 1

            label = QLabel(self)
            label.setWordWrap(True)
            label.setText(
                self.tr("It is encrypted using {0} at {1} bits, "
                        "with {2} for message authentication and "
                        "{3} as key exchange mechanism.\n\n").format(
                            cipher.encryptionMethod(), cipher.usedBits(),
                            cipher.authenticationMethod(),
                            cipher.keyExchangeMethod()))
            layout.addWidget(label, rows, 1)
            rows += 1

    def showAt(self, pos):
        """
        Public method to show the widget.
        
        @param pos position to show at (QPoint)
        """
        self.adjustSize()
        xpos = pos.x() - self.width()
        if xpos < 0:
            xpos = 10
        p = QPoint(xpos, pos.y() + 10)
        self.move(p)
        self.show()

    def __showCertificateInfos(self):
        """
        Private slot to show certificate information.
        """
        from .E5SslCertificatesInfoDialog import E5SslCertificatesInfoDialog
        dlg = E5SslCertificatesInfoDialog(
            self.__configuration.peerCertificateChain())
        dlg.exec_()

    def accept(self):
        """
        Public method to accept the widget.
        """
        self.close()
Exemple #30
0
class HttpWindow(QDialog):
    def __init__(self, parent=None):
        super(HttpWindow, self).__init__(parent)

        self.url = QUrl()
        self.qnam = QNetworkAccessManager()
        self.reply = None
        self.outFile = None
        self.httpGetId = 0
        self.httpRequestAborted = False

        self.urlLineEdit = QLineEdit("https://www.qt.io")

        urlLabel = QLabel("&URL:")
        urlLabel.setBuddy(self.urlLineEdit)
        self.statusLabel = QLabel(
            "Please enter the URL of a file you want to download."
        )
        self.statusLabel.setWordWrap(True)

        self.downloadButton = QPushButton("Download")
        self.downloadButton.setDefault(True)
        self.quitButton = QPushButton("Quit")
        self.quitButton.setAutoDefault(False)

        buttonBox = QDialogButtonBox()
        buttonBox.addButton(self.downloadButton, QDialogButtonBox.ActionRole)
        buttonBox.addButton(self.quitButton, QDialogButtonBox.RejectRole)

        self.progressDialog = QProgressDialog(self)

        self.urlLineEdit.textChanged.connect(self.enableDownloadButton)
        self.qnam.authenticationRequired.connect(self.slotAuthenticationRequired)
        self.qnam.sslErrors.connect(self.sslErrors)
        self.progressDialog.canceled.connect(self.cancelDownload)
        self.downloadButton.clicked.connect(self.downloadFile)
        self.quitButton.clicked.connect(self.close)

        topLayout = QHBoxLayout()
        topLayout.addWidget(urlLabel)
        topLayout.addWidget(self.urlLineEdit)

        mainLayout = QVBoxLayout()
        mainLayout.addLayout(topLayout)
        mainLayout.addWidget(self.statusLabel)
        mainLayout.addWidget(buttonBox)
        self.setLayout(mainLayout)

        self.setWindowTitle("HTTP")
        self.urlLineEdit.setFocus()

    def startRequest(self, url):
        self.reply = self.qnam.get(QNetworkRequest(url))
        self.reply.finished.connect(self.httpFinished)
        self.reply.readyRead.connect(self.httpReadyRead)
        self.reply.downloadProgress.connect(self.updateDataReadProgress)

    def downloadFile(self):
        self.url = QUrl(self.urlLineEdit.text())
        fileInfo = QFileInfo(self.url.path())
        fileName = fileInfo.fileName()

        if not fileName:
            fileName = "index.html"

        if QFile.exists(fileName):
            ret = QMessageBox.question(
                self,
                "HTTP",
                "There already exists a file called %s in the current "
                "directory. Overwrite?" % fileName,
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No,
            )

            if ret == QMessageBox.No:
                return

            QFile.remove(fileName)

        self.outFile = QFile(fileName)
        if not self.outFile.open(QIODevice.WriteOnly):
            QMessageBox.information(
                self,
                "HTTP",
                "Unable to save the file %s: %s."
                % (fileName, self.outFile.errorString()),
            )
            self.outFile = None
            return

        self.progressDialog.setWindowTitle("HTTP")
        self.progressDialog.setLabelText("Downloading %s." % fileName)
        self.downloadButton.setEnabled(False)

        self.httpRequestAborted = False
        self.startRequest(self.url)

    def cancelDownload(self):
        self.statusLabel.setText("Download canceled.")
        self.httpRequestAborted = True
        if self.reply is not None:
            self.reply.abort()
        self.downloadButton.setEnabled(True)

    def httpFinished(self):
        if self.httpRequestAborted:
            if self.outFile is not None:
                self.outFile.close()
                self.outFile.remove()
                self.outFile = None

            self.reply.deleteLater()
            self.reply = None
            self.progressDialog.hide()
            return

        self.progressDialog.hide()
        self.outFile.flush()
        self.outFile.close()

        redirectionTarget = self.reply.attribute(
            QNetworkRequest.RedirectionTargetAttribute
        )

        if self.reply.error():
            self.outFile.remove()
            QMessageBox.information(
                self, "HTTP", "Download failed: %s." % self.reply.errorString()
            )
            self.downloadButton.setEnabled(True)
        elif redirectionTarget is not None:
            newUrl = self.url.resolved(redirectionTarget)

            ret = QMessageBox.question(
                self,
                "HTTP",
                "Redirect to %s?" % newUrl.toString(),
                QMessageBox.Yes | QMessageBox.No,
            )

            if ret == QMessageBox.Yes:
                self.url = newUrl
                self.reply.deleteLater()
                self.reply = None
                self.outFile.open(QIODevice.WriteOnly)
                self.outFile.resize(0)
                self.startRequest(self.url)
                return
        else:
            fileName = QFileInfo(QUrl(self.urlLineEdit.text()).path()).fileName()
            self.statusLabel.setText(
                "Downloaded %s to %s." % (fileName, QDir.currentPath())
            )

            self.downloadButton.setEnabled(True)

        self.reply.deleteLater()
        self.reply = None
        self.outFile = None

    def httpReadyRead(self):
        if self.outFile is not None:
            self.outFile.write(self.reply.readAll())

    def updateDataReadProgress(self, bytesRead, totalBytes):
        if self.httpRequestAborted:
            return

        self.progressDialog.setMaximum(totalBytes)
        self.progressDialog.setValue(bytesRead)

    def enableDownloadButton(self):
        self.downloadButton.setEnabled(self.urlLineEdit.text() != "")

    def slotAuthenticationRequired(self, authenticator):
        import os
        from PyQt5 import uic

        ui = os.path.join(os.path.dirname(__file__), "authenticationdialog.ui")
        dlg = uic.loadUi(ui)
        dlg.adjustSize()
        dlg.siteDescription.setText(
            "%s at %s" % (authenticator.realm(), self.url.host())
        )

        dlg.userEdit.setText(self.url.userName())
        dlg.passwordEdit.setText(self.url.password())

        if dlg.exec_() == QDialog.Accepted:
            authenticator.setUser(dlg.userEdit.text())
            authenticator.setPassword(dlg.passwordEdit.text())

    def sslErrors(self, reply, errors):
        errorString = ", ".join([str(error.errorString()) for error in errors])

        ret = QMessageBox.warning(
            self,
            "HTTP Example",
            "One or more SSL errors has occurred: %s" % errorString,
            QMessageBox.Ignore | QMessageBox.Abort,
        )

        if ret == QMessageBox.Ignore:
            self.reply.ignoreSslErrors()
Exemple #31
0
    def accept(self):
        section = CFG_SECTION_IPFSD

        if self.ui.groupBoxDaemon.isChecked():
            self.sManager.setTrue(section, CFG_KEY_ENABLED)
        else:
            self.sManager.setFalse(section, CFG_KEY_ENABLED)

        self.setS(section, CFG_KEY_SWARMPORT, self.ui.ipfsdSwarmPort.text())
        self.setS(section, CFG_KEY_SWARMPORT_QUIC,
                  self.ui.ipfsdSwarmPortQuic.text())
        self.sManager.setBoolFrom(section, CFG_KEY_SWARM_QUIC,
                                  self.isChecked(self.ui.checkBoxQuic))
        self.setS(section, CFG_KEY_APIPORT, self.ui.ipfsdApiPort.text())
        self.setS(section, CFG_KEY_HTTPGWPORT, self.ui.ipfsdGwPort.text())
        self.setS(section, CFG_KEY_SWARMLOWWATER, self.ui.swarmMinConns.text())
        self.setS(
            section,
            CFG_KEY_SWARMHIGHWATER,
            self.ui.swarmMaxConns.text())
        self.setS(section, CFG_KEY_STORAGEMAX, self.ui.storageMax.text())
        self.setS(section, CFG_KEY_ROUTINGMODE,
                  self.ui.routingMode.currentText())
        self.setS(section, CFG_KEY_PUBSUB_ROUTER,
                  self.ui.pubsubRoutingMode.currentText())
        self.sManager.setBoolFrom(section, CFG_KEY_HTTPGWWRITABLE,
                                  self.isChecked(self.ui.writableHttpGw))
        self.sManager.setBoolFrom(section, CFG_KEY_NAMESYS_PUBSUB,
                                  self.isChecked(self.ui.namesysPubsub))
        self.sManager.setBoolFrom(section, CFG_KEY_FILESTORE,
                                  self.isChecked(self.ui.fileStore))
        self.sManager.setBoolFrom(section, CFG_KEY_IPFSD_DETACHED,
                                  self.isChecked(self.ui.keepDaemonRunning))

        section = CFG_SECTION_IPFSCONN1
        self.setS(section, CFG_KEY_HOST, self.ui.customIpfsHost.text())
        self.setS(section, CFG_KEY_APIPORT, self.ui.customIpfsApiPort.text())
        self.setS(section, CFG_KEY_HTTPGWPORT, self.ui.customIpfsGwPort.text())

        section = CFG_SECTION_BROWSER
        self.setS(section, CFG_KEY_HOMEURL, self.ui.home.text())

        cSet('defaultWebProfile',
             self.ui.comboDefaultWebProfile.currentText(),
             mod='galacteek.browser.webprofiles')

        section = CFG_SECTION_HISTORY
        cSet('enabled', self.isChecked(self.ui.urlHistoryEnable),
             mod='galacteek.ui.history')

        cSet('zoom.default', self.ui.webEngineDefaultZoom.value(),
             mod='galacteek.ui.browser')

        idx = self.ui.language.currentIndex()
        langCode = self.ui.language.itemData(idx)

        curLang = cGet('language', mod='galacteek.application')
        if langCode != curLang:
            cSet('language', langCode, mod='galacteek.application')
            self.app.setupTranslator()

        section = CFG_SECTION_ETHEREUM

        if self.ui.groupBoxEth.isChecked():
            self.sManager.setTrue(section, CFG_KEY_ENABLED)
        else:
            self.sManager.setFalse(section, CFG_KEY_ENABLED)

        rpcUrl = QUrl(self.ui.ethRpcUrl.text())

        if not rpcUrl.isValid() or not rpcUrl.scheme() in [
                'http', 'https', 'wss'] or not rpcUrl.host():
            return messageBox(
                'Invalid Ethereum RPC URL (scheme should be http or wss)'
            )

        self.setS(section, CFG_KEY_PROVIDERTYPE,
                  self.ui.ethProvType.currentText())
        self.setS(section, CFG_KEY_RPCURL, rpcUrl.toString())

        self.app.urlHistory.historyConfigChanged.emit(
            self.sManager.urlHistoryEnabled)

        self.sManager.sync()
        self.sManager.changed = True

        self.done(1)
Exemple #32
0
class ClickToFlash(QWidget, Ui_ClickToFlash):
    """
    Class implementing the Flash blocker.
    """
    _acceptedUrl = QUrl()
    _acceptedArgNames = []
    _acceptedArgValues = []

    def __init__(self,
                 plugin,
                 mimeType,
                 url,
                 argumentNames,
                 argumentValues,
                 parent=None):
        """
        Constructor
        
        @param plugin reference to the plug-in (ClickToFlashPlugin)
        @param mimeType MIME type for the plug-in (string)
        @param url requested URL (QUrl)
        @param argumentNames list of argument names (list of strings)
        @param argumentValues list of argument values (list of strings)
        @param parent reference to the parent widget (QWidget)
        """
        super(ClickToFlash, self).__init__(parent)

        # Check AdBlock first
        import Helpviewer.HelpWindow
        manager = Helpviewer.HelpWindow.HelpWindow.adBlockManager()
        if manager.isEnabled():
            urlString = bytes(url.toEncoded()).decode()
            urlDomain = url.host()
            for subscription in manager.subscriptions():
                blockedRule = subscription.match(QNetworkRequest(url),
                                                 urlDomain, urlString)
                if blockedRule:
                    QTimer.singleShot(200, self.__hideAdBlocked)
                    return

        self.setupUi(self)

        self.__swapping = False
        self.__element = QWebElement()

        self.__plugin = plugin
        self.__url = QUrl(url)
        self.__argumentNames = argumentNames[:]
        self.__argumentValues = argumentValues[:]
        self.__mimeType = mimeType

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.__showContextMenu)
        self.setToolTip(self.__url.toString())

        iconName = plugin.getIconName(mimeType)
        if iconName:
            self.loadFlashButton.setIcon(UI.PixmapCache.getIcon(iconName))
        else:
            self.loadFlashButton.setText(self.tr("Load"))

    @pyqtSlot()
    def on_loadFlashButton_clicked(self):
        """
        Private slot handling the flash activation.
        """
        self.__load()

    def __showContextMenu(self):
        """
        Private slot to show the context menu.
        """
        menu = QMenu()
        act = menu.addAction(self.tr("Object blocked by ClickToFlash"))
        font = act.font()
        font.setBold(True)
        act.setFont(font)
        menu.addAction(self.tr("Show information about object"),
                       self.__showInfo)
        menu.addSeparator()
        menu.addAction(self.tr("Load"), self.__load)
        menu.addAction(self.tr("Delete object"), self.__hideAdBlocked)
        menu.addSeparator()
        host = self.__url.host()
        add = menu.addAction(
            self.tr("Add '{0}' to Whitelist").format(host),
            self.__addToWhitelist)
        remove = menu.addAction(
            self.tr("Remove '{0}' from Whitelist").format(host),
            self.__removeFromWhitelist)
        onWhitelist = self.__plugin.onWhitelist(host)
        add.setEnabled(not onWhitelist)
        remove.setEnabled(onWhitelist)
        menu.addSeparator()
        menu.addAction(self.tr("Configure Whitelist"), self.__configure)
        menu.actions()[0].setEnabled(False)

        menu.exec_(QCursor.pos())

    def swapping(self):
        """
        Public method to check, if the plug-in is swapping.
        
        @return flag indicating the swapping status (boolean)
        """
        return self.__swapping

    def __configure(self):
        """
        Private slot to configure the whitelist.
        """
        self.__plugin.configure()

    def __addToWhitelist(self):
        """
        Private slot to add the host to the whitelist.
        """
        self.__plugin.addToWhitelist(self.__url.host())

    def __removeFromWhitelist(self):
        """
        Private slot to remove the host from the whitelist.
        """
        self.__plugin.removeFromWhitelist(self.__url.host())

    def __load(self, all=False):
        """
        Private slot to load the flash content.
        
        @param all flag indicating to load all flash players. (boolean)
        """
        self.__findElement()
        if not self.__element.isNull():
            substitute = self.__element.clone()
            substitute.setAttribute("type", self.__mimeType)
            self.__element.replace(substitute)

            ClickToFlash._acceptedUrl = self.__url
            ClickToFlash._acceptedArgNames = self.__argumentNames
            ClickToFlash._acceptedArgValues = self.__argumentValues

    def __findElement(self):
        """
        Private method to find the element belonging to this ClickToFlash
        instance.
        """
        parent = self.parentWidget()
        view = None
        while parent is not None:
            if isinstance(parent, QWebView):
                view = parent
                break
            parent = parent.parentWidget()
        if view is None:
            return

        objectPos = view.mapFromGlobal(
            self.loadFlashButton.mapToGlobal(self.loadFlashButton.pos()))
        objectFrame = view.page().frameAt(objectPos)
        hitResult = QWebHitTestResult()
        hitElement = QWebElement()

        if objectFrame is not None:
            hitResult = objectFrame.hitTestContent(objectPos)
            hitElement = hitResult.element()

        if not hitElement.isNull() and \
           hitElement.tagName().lower() in ["embed", "object"]:
            self.__element = hitElement
            return

        # hit test failed, trying to find element by src
        # attribute in elements of all frames on page (although less accurate
        frames = []
        frames.append(view.page().mainFrame())
        while frames:
            frame = frames.pop(0)
            if not frame:
                continue
            docElement = frame.documentElement()
            elements = QWebElementCollection()
            elements.append(docElement.findAll("embed"))
            elements.append(docElement.findAll("object"))

            for element in elements:
                if not self.__checkElement(element) and \
                   not self.__checkUrlOnElement(element, view):
                    continue
                self.__element = element
                return
            frames.extend(frame.childFrames())

    def __checkUrlOnElement(self, element, view):
        """
        Private slot to check the URL of an element.
        
        @param element reference to the element to check (QWebElement)
        @param view reference to the view object (QWebView)
        @return flag indicating a positive result (boolean)
        """
        checkString = element.attribute("src")
        if checkString == "":
            checkString = element.attribute("data")
        if checkString == "":
            checkString = element.attribute("value")

        checkString = view.url().resolved(QUrl(checkString)).toString(
            QUrl.RemoveQuery)
        return self.__url.toEncoded().contains(
            QByteArray(checkString.encode("utf-8")))

    def __checkElement(self, element):
        """
        Private slot to check an element against the saved arguments.
        
        @param element reference to the element to check (QWebElement)
        @return flag indicating a positive result (boolean)
        """
        if self.__argumentNames == element.attributeNames():
            for name in self.__argumentNames:
                if element.attribute(name) not in self.__argumentValues:
                    return False

            return True

        return False

    def __hideAdBlocked(self):
        """
        Private slot to hide the object.
        """
        self.__findElement()
        if not self.__element.isNull():
            self.__element.setStyleProperty("display", "none")
        else:
            self.hide()

    def __showInfo(self):
        """
        Private slot to show information about the blocked object.
        """
        dlg = QDialog()
        dlg.setWindowTitle(self.tr("Flash Object"))
        dlg.setSizeGripEnabled(True)
        layout = QFormLayout(dlg)
        layout.addRow(QLabel(self.tr("<b>Attribute Name</b>")),
                      QLabel(self.tr("<b>Value</b>")))

        index = 0
        for name in self.__argumentNames:
            nameLabel = QLabel(self.__elide(name, length=30))
            value = self.__argumentValues[index]
            valueLabel = QLabel(self.__elide(value, length=60))
            valueLabel.setTextInteractionFlags(Qt.TextSelectableByMouse
                                               | Qt.LinksAccessibleByMouse)
            layout.addRow(nameLabel, valueLabel)

            index += 1

        if index == 0:
            layout.addRow(QLabel(self.tr("No information available.")))

        dlg.setMaximumHeight(500)
        dlg.setMaximumWidth(500)
        dlg.exec_()

    def __elide(self, txt, mode=Qt.ElideMiddle, length=40):
        """
        Private method to elide some text.
        
        @param txt text to be elided (string)
        @keyparam mode elide mode (Qt.TextElideMode)
        @keyparam length amount of characters to be used (integer)
        @return the elided text (string)
        """
        if mode == Qt.ElideNone or len(txt) < length:
            return txt
        elif mode == Qt.ElideLeft:
            return "...{0}".format(txt[-length:])
        elif mode == Qt.ElideMiddle:
            return "{0}...{1}".format(txt[:length // 2], txt[-(length // 2):])
        elif mode == Qt.ElideRight:
            return "{0}...".format(txt[:length])
        else:
            # just in case
            return txt

    @classmethod
    def isAlreadyAccepted(cls, url, argumentNames, argumentValues):
        """
        Class method to check, if the given parameter combination is being
        accepted.
        
        @param url URL to be checked for (QUrl)
        @param argumentNames argument names to be checked for (list of strings)
        @param argumentValues argument values to be checked for (list of
            strings)
        @return flag indicating that this was already accepted (boolean)
        """
        return url == cls._acceptedUrl and \
            argumentNames == cls._acceptedArgNames and \
            argumentValues == cls._acceptedArgValues
Exemple #33
0
class WebView(QWebView):

    """One browser tab in TabbedBrowser.

    Our own subclass of a QWebView with some added bells and whistles.

    Attributes:
        hintmanager: The HintManager instance for this view.
        progress: loading progress of this page.
        scroll_pos: The current scroll position as (x%, y%) tuple.
        statusbar_message: The current javascript statusbar message.
        inspector: The QWebInspector used for this webview.
        load_status: loading status of this page (index into LoadStatus)
        viewing_source: Whether the webview is currently displaying source
                        code.
        keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
                   load.
        registry: The ObjectRegistry associated with this tab.
        tab_id: The tab ID of the view.
        win_id: The window ID of the view.
        search_text: The text of the last search.
        search_flags: The search flags of the last search.
        _has_ssl_errors: Whether SSL errors occurred during loading.
        _zoom: A NeighborList with the zoom levels.
        _old_scroll_pos: The old scroll position.
        _check_insertmode: If True, in mouseReleaseEvent we should check if we
                           need to enter/leave insert mode.
        _default_zoom_changed: Whether the zoom was changed from the default.
        _ignore_wheel_event: Ignore the next wheel event.
                             See https://github.com/The-Compiler/qutebrowser/issues/395

    Signals:
        scroll_pos_changed: Scroll percentage of current tab changed.
                            arg 1: x-position in %.
                            arg 2: y-position in %.
        linkHovered: QWebPages linkHovered signal exposed.
        load_status_changed: The loading status changed
        url_text_changed: Current URL string changed.
        shutting_down: Emitted when the view is shutting down.
    """

    scroll_pos_changed = pyqtSignal(int, int)
    linkHovered = pyqtSignal(str, str, str)
    load_status_changed = pyqtSignal(str)
    url_text_changed = pyqtSignal(str)
    shutting_down = pyqtSignal()

    def __init__(self, win_id, parent=None):
        super().__init__(parent)
        if sys.platform == 'darwin' and qtutils.version_check('5.4'):
            # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
            # See https://github.com/The-Compiler/qutebrowser/issues/462
            self.setStyle(QStyleFactory.create('Fusion'))
        self.win_id = win_id
        self.load_status = LoadStatus.none
        self._check_insertmode = False
        self.inspector = None
        self.scroll_pos = (-1, -1)
        self.statusbar_message = ''
        self._old_scroll_pos = (-1, -1)
        self._zoom = None
        self._has_ssl_errors = False
        self._ignore_wheel_event = False
        self.keep_icon = False
        self.search_text = None
        self.search_flags = 0
        self.selection_enabled = False
        self.init_neighborlist()
        self._set_bg_color()
        cfg = objreg.get('config')
        cfg.changed.connect(self.init_neighborlist)
        # For some reason, this signal doesn't get disconnected automatically
        # when the WebView is destroyed on older PyQt versions.
        # See https://github.com/The-Compiler/qutebrowser/issues/390
        self.destroyed.connect(functools.partial(
            cfg.changed.disconnect, self.init_neighborlist))
        self.cur_url = QUrl()
        self.progress = 0
        self.registry = objreg.ObjectRegistry()
        self.tab_id = next(tab_id_gen)
        tab_registry = objreg.get('tab-registry', scope='window',
                                  window=win_id)
        tab_registry[self.tab_id] = self
        objreg.register('webview', self, registry=self.registry)
        page = self._init_page()
        hintmanager = hints.HintManager(win_id, self.tab_id, self)
        hintmanager.mouse_event.connect(self.on_mouse_event)
        hintmanager.start_hinting.connect(page.on_start_hinting)
        hintmanager.stop_hinting.connect(page.on_stop_hinting)
        objreg.register('hintmanager', hintmanager, registry=self.registry)
        mode_manager = objreg.get('mode-manager', scope='window',
                                  window=win_id)
        mode_manager.entered.connect(self.on_mode_entered)
        mode_manager.left.connect(self.on_mode_left)
        self.viewing_source = False
        self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100)
        self._default_zoom_changed = False
        if config.get('input', 'rocker-gestures'):
            self.setContextMenuPolicy(Qt.PreventContextMenu)
        self.urlChanged.connect(self.on_url_changed)
        self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
        objreg.get('config').changed.connect(self.on_config_changed)

    def _init_page(self):
        """Initialize the QWebPage used by this view."""
        page = webpage.BrowserPage(self.win_id, self.tab_id, self)
        self.setPage(page)
        page.linkHovered.connect(self.linkHovered)
        page.mainFrame().loadStarted.connect(self.on_load_started)
        page.mainFrame().loadFinished.connect(self.on_load_finished)
        page.statusBarMessage.connect(
            lambda msg: setattr(self, 'statusbar_message', msg))
        page.networkAccessManager().sslErrors.connect(
            lambda *args: setattr(self, '_has_ssl_errors', True))
        return page

    def __repr__(self):
        url = utils.elide(self.url().toDisplayString(), 50)
        return utils.get_repr(self, tab_id=self.tab_id, url=url)

    def __del__(self):
        # Explicitly releasing the page here seems to prevent some segfaults
        # when quitting.
        # Copied from:
        # https://code.google.com/p/webscraping/source/browse/webkit.py#325
        try:
            self.setPage(None)
        except RuntimeError:
            # It seems sometimes Qt has already deleted the QWebView and we
            # get: RuntimeError: wrapped C/C++ object of type WebView has been
            # deleted
            pass

    def _set_load_status(self, val):
        """Setter for load_status."""
        if not isinstance(val, LoadStatus):
            raise TypeError("Type {} is no LoadStatus member!".format(val))
        log.webview.debug("load status for {}: {}".format(repr(self), val))
        self.load_status = val
        self.load_status_changed.emit(val.name)

    def _set_bg_color(self):
        """Set the webpage background color as configured."""
        col = config.get('colors', 'webpage.bg')
        palette = self.palette()
        if col is None:
            col = self.style().standardPalette().color(QPalette.Base)
        palette.setColor(QPalette.Base, col)
        self.setPalette(palette)

    @pyqtSlot(str, str)
    def on_config_changed(self, section, option):
        """Reinitialize the zoom neighborlist if related config changed."""
        if section == 'ui' and option in ('zoom-levels', 'default-zoom'):
            if not self._default_zoom_changed:
                self.setZoomFactor(float(config.get('ui', 'default-zoom')) /
                                   100)
            self._default_zoom_changed = False
            self.init_neighborlist()
        elif section == 'input' and option == 'rocker-gestures':
            if config.get('input', 'rocker-gestures'):
                self.setContextMenuPolicy(Qt.PreventContextMenu)
            else:
                self.setContextMenuPolicy(Qt.DefaultContextMenu)
        elif section == 'colors' and option == 'webpage.bg':
            self._set_bg_color()

    def init_neighborlist(self):
        """Initialize the _zoom neighborlist."""
        levels = config.get('ui', 'zoom-levels')
        self._zoom = usertypes.NeighborList(
            levels, mode=usertypes.NeighborList.Modes.block)
        self._zoom.fuzzyval = config.get('ui', 'default-zoom')

    def _mousepress_backforward(self, e):
        """Handle back/forward mouse button presses.

        Args:
            e: The QMouseEvent.
        """
        if e.button() in (Qt.XButton1, Qt.LeftButton):
            # Back button on mice which have it, or rocker gesture
            if self.page().history().canGoBack():
                self.back()
            else:
                message.error(self.win_id, "At beginning of history.",
                              immediately=True)
        elif e.button() in (Qt.XButton2, Qt.RightButton):
            # Forward button on mice which have it, or rocker gesture
            if self.page().history().canGoForward():
                self.forward()
            else:
                message.error(self.win_id, "At end of history.",
                              immediately=True)

    def _mousepress_insertmode(self, e):
        """Switch to insert mode when an editable element was clicked.

        Args:
            e: The QMouseEvent.
        """
        pos = e.pos()
        frame = self.page().frameAt(pos)
        if frame is None:
            # This happens when we click inside the webview, but not actually
            # on the QWebPage - for example when clicking the scrollbar
            # sometimes.
            log.mouse.debug("Clicked at {} but frame is None!".format(pos))
            return
        # You'd think we have to subtract frame.geometry().topLeft() from the
        # position, but it seems QWebFrame::hitTestContent wants a position
        # relative to the QWebView, not to the frame. This makes no sense to
        # me, but it works this way.
        hitresult = frame.hitTestContent(pos)
        if hitresult.isNull():
            # For some reason, the whole hit result can be null sometimes (e.g.
            # on doodle menu links). If this is the case, we schedule a check
            # later (in mouseReleaseEvent) which uses webelem.focus_elem.
            log.mouse.debug("Hitresult is null!")
            self._check_insertmode = True
            return
        try:
            elem = webelem.WebElementWrapper(hitresult.element())
        except webelem.IsNullError:
            # For some reason, the hit result element can be a null element
            # sometimes (e.g. when clicking the timetable fields on
            # http://www.sbb.ch/ ). If this is the case, we schedule a check
            # later (in mouseReleaseEvent) which uses webelem.focus_elem.
            log.mouse.debug("Hitresult element is null!")
            self._check_insertmode = True
            return
        if ((hitresult.isContentEditable() and elem.is_writable()) or
                elem.is_editable()):
            log.mouse.debug("Clicked editable element!")
            modeman.enter(self.win_id, usertypes.KeyMode.insert, 'click',
                          only_if_normal=True)
        else:
            log.mouse.debug("Clicked non-editable element!")
            if config.get('input', 'auto-leave-insert-mode'):
                modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert,
                                    'click')

    def mouserelease_insertmode(self):
        """If we have an insertmode check scheduled, handle it."""
        if not self._check_insertmode:
            return
        self._check_insertmode = False
        try:
            elem = webelem.focus_elem(self.page().currentFrame())
        except (webelem.IsNullError, RuntimeError):
            log.mouse.warning("Element/page vanished!")
            return
        if elem.is_editable():
            log.mouse.debug("Clicked editable element (delayed)!")
            modeman.enter(self.win_id, usertypes.KeyMode.insert,
                          'click-delayed', only_if_normal=True)
        else:
            log.mouse.debug("Clicked non-editable element (delayed)!")
            if config.get('input', 'auto-leave-insert-mode'):
                modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert,
                                    'click-delayed')

    def _mousepress_opentarget(self, e):
        """Set the open target when something was clicked.

        Args:
            e: The QMouseEvent.
        """
        if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
            background_tabs = config.get('tabs', 'background-tabs')
            if e.modifiers() & Qt.ShiftModifier:
                background_tabs = not background_tabs
            if background_tabs:
                target = usertypes.ClickTarget.tab_bg
            else:
                target = usertypes.ClickTarget.tab
            self.page().open_target = target
            log.mouse.debug("Middle click, setting target: {}".format(target))
        else:
            self.page().open_target = usertypes.ClickTarget.normal
            log.mouse.debug("Normal click, setting normal target")

    def shutdown(self):
        """Shut down the webview."""
        self.shutting_down.emit()
        # We disable javascript because that prevents some segfaults when
        # quitting it seems.
        log.destroy.debug("Shutting down {!r}.".format(self))
        settings = self.settings()
        settings.setAttribute(QWebSettings.JavascriptEnabled, False)
        self.stop()
        self.page().shutdown()

    def openurl(self, url):
        """Open a URL in the browser.

        Args:
            url: The URL to load as QUrl
        """
        qtutils.ensure_valid(url)
        urlstr = url.toDisplayString()
        log.webview.debug("New title: {}".format(urlstr))
        self.titleChanged.emit(urlstr)
        self.cur_url = url
        self.url_text_changed.emit(url.toDisplayString())
        self.load(url)
        if url.scheme() == 'qute':
            frame = self.page().mainFrame()
            frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge)

    def add_js_bridge(self):
        """Add the javascript bridge for qute:... pages."""
        frame = self.sender()
        if frame.url().scheme() == 'qute':
            bridge = objreg.get('js-bridge')
            frame.addToJavaScriptWindowObject('qute', bridge)

    def zoom_perc(self, perc, fuzzyval=True):
        """Zoom to a given zoom percentage.

        Args:
            perc: The zoom percentage as int.
            fuzzyval: Whether to set the NeighborLists fuzzyval.
        """
        if fuzzyval:
            self._zoom.fuzzyval = int(perc)
        if perc < 0:
            raise ValueError("Can't zoom {}%!".format(perc))
        self.setZoomFactor(float(perc) / 100)
        self._default_zoom_changed = True

    def zoom(self, offset):
        """Increase/Decrease the zoom level.

        Args:
            offset: The offset in the zoom level list.

        Return:
            The new zoom percentage.
        """
        level = self._zoom.getitem(offset)
        self.zoom_perc(level, fuzzyval=False)
        return level

    @pyqtSlot('QUrl')
    def on_url_changed(self, url):
        """Update cur_url when URL has changed.

        If the URL is invalid, we just ignore it here.
        """
        if url.isValid():
            self.cur_url = url
            self.url_text_changed.emit(url.toDisplayString())
            if not self.title():
                self.titleChanged.emit(self.url().toDisplayString())

    @pyqtSlot('QMouseEvent')
    def on_mouse_event(self, evt):
        """Post a new mouse event from a hintmanager."""
        log.modes.debug("Hint triggered, focusing {!r}".format(self))
        self.setFocus()
        QApplication.postEvent(self, evt)

    @pyqtSlot()
    def on_load_started(self):
        """Leave insert/hint mode and set vars when a new page is loading."""
        self.progress = 0
        self.viewing_source = False
        self._has_ssl_errors = False
        self._set_load_status(LoadStatus.loading)

    @pyqtSlot()
    def on_load_finished(self):
        """Handle a finished page load.

        We don't take loadFinished's ok argument here as it always seems to be
        true when the QWebPage has an ErrorPageExtension implemented.
        See https://github.com/The-Compiler/qutebrowser/issues/84
        """
        ok = not self.page().error_occurred
        if ok and not self._has_ssl_errors:
            self._set_load_status(LoadStatus.success)
        elif ok:
            self._set_load_status(LoadStatus.warn)
        else:
            self._set_load_status(LoadStatus.error)
        if not self.title():
            self.titleChanged.emit(self.url().toDisplayString())
        self._handle_auto_insert_mode(ok)

    def _handle_auto_insert_mode(self, ok):
        """Handle auto-insert-mode after loading finished."""
        ai = False
        for mode in (usertypes.KeyMode.hint, usertypes.KeyMode.insert,
                     usertypes.KeyMode.caret, usertypes.KeyMode.passthrough):
            modeman.maybe_leave(self.win_id, mode, 'load finished')
        for r in pt_masks:
            if re.match(r, self.cur_url.host()) is not None:
                print(re.match(r, self.cur_url.host()), re.match(r, self.cur_url.host()).groups())
                ai = True
                break
        if ai:
            modeman.enter(self.win_id, usertypes.KeyMode.passthrough,
                          'load finished', only_if_normal=True)
            return
        if not config.get('input', 'auto-insert-mode'):
            return
        mode_manager = objreg.get('mode-manager', scope='window',
                                  window=self.win_id)
        cur_mode = mode_manager.mode
        if cur_mode == usertypes.KeyMode.insert or not ok:
            return
        frame = self.page().currentFrame()
        try:
            elem = webelem.WebElementWrapper(frame.findFirstElement(':focus'))
        except webelem.IsNullError:
            log.webview.debug("Focused element is null!")
        log.modes.debug("focus element: {}".format(repr(elem)))
        print("bazqux" in self.cur_url.host(), self.cur_url.host())
        if elem.is_editable() or rss:
            modeman.enter(self.win_id, usertypes.KeyMode.insert,
                          'load finished', only_if_normal=True)

    @pyqtSlot(usertypes.KeyMode)
    def on_mode_entered(self, mode):
        """Ignore attempts to focus the widget if in any status-input mode."""
        if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
                    usertypes.KeyMode.yesno):
            log.webview.debug("Ignoring focus because mode {} was "
                              "entered.".format(mode))
            self.setFocusPolicy(Qt.NoFocus)
        elif mode == usertypes.KeyMode.caret:
            settings = self.settings()
            settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
            self.selection_enabled = bool(self.page().selectedText())

            if self.isVisible():
                # Sometimes the caret isn't immediately visible, but unfocusing
                # and refocusing it fixes that.
                self.clearFocus()
                self.setFocus(Qt.OtherFocusReason)

                # Move the caret to the first element in the viewport if there
                # isn't any text which is already selected.
                #
                # Note: We can't use hasSelection() here, as that's always
                # true in caret mode.
                if not self.page().selectedText():
                    self.page().currentFrame().evaluateJavaScript(
                        utils.read_file('javascript/position_caret.js'))

    @pyqtSlot(usertypes.KeyMode)
    def on_mode_left(self, mode):
        """Restore focus policy if status-input modes were left."""
        if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
                    usertypes.KeyMode.yesno):
            log.webview.debug("Restoring focus policy because mode {} was "
                              "left.".format(mode))
        elif mode == usertypes.KeyMode.caret:
            settings = self.settings()
            if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
                if self.selection_enabled and self.hasSelection():
                    # Remove selection if it exists
                    self.triggerPageAction(QWebPage.MoveToNextChar)
                settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
                self.selection_enabled = False

        self.setFocusPolicy(Qt.WheelFocus)

    def search(self, text, flags):
        """Search for text in the current page.

        Args:
            text: The text to search for.
            flags: The QWebPage::FindFlags.
        """
        log.webview.debug("Searching with text '{}' and flags "
                          "0x{:04x}.".format(text, int(flags)))
        old_scroll_pos = self.scroll_pos
        flags = QWebPage.FindFlags(flags)
        found = self.findText(text, flags)
        backward = flags & QWebPage.FindBackward

        if not found and not flags & QWebPage.HighlightAllOccurrences and text:
            # User disabled wrapping; but findText() just returns False. If we
            # have a selection, we know there's a match *somewhere* on the page
            if (not flags & QWebPage.FindWrapsAroundDocument and
                    self.hasSelection()):
                if not backward:
                    message.warning(self.win_id, "Search hit BOTTOM without "
                                    "match for: {}".format(text),
                                    immediately=True)
                else:
                    message.warning(self.win_id, "Search hit TOP without "
                                    "match for: {}".format(text),
                                    immediately=True)
            else:
                message.error(self.win_id, "Text '{}' not found on "
                              "page!".format(text), immediately=True)
        else:
            def check_scroll_pos():
                """Check if the scroll position got smaller and show info."""
                if not backward and self.scroll_pos < old_scroll_pos:
                    message.info(self.win_id, "Search hit BOTTOM, continuing "
                                 "at TOP", immediately=True)
                elif backward and self.scroll_pos > old_scroll_pos:
                    message.info(self.win_id, "Search hit TOP, continuing at "
                                 "BOTTOM", immediately=True)
            # We first want QWebPage to refresh.
            QTimer.singleShot(0, check_scroll_pos)

    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.
        """
        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)
        return tabbed_browser.tabopen(background=False)

    def paintEvent(self, e):
        """Extend paintEvent to emit a signal if the scroll position changed.

        This is a bit of a hack: We listen to repaint requests here, in the
        hope a repaint will always be requested when scrolling, and if the
        scroll position actually changed, we emit a signal.

        Args:
            e: The QPaintEvent.

        Return:
            The superclass event return value.
        """
        frame = self.page().mainFrame()
        new_pos = (frame.scrollBarValue(Qt.Horizontal),
                   frame.scrollBarValue(Qt.Vertical))
        if self._old_scroll_pos != new_pos:
            self._old_scroll_pos = new_pos
            m = (frame.scrollBarMaximum(Qt.Horizontal),
                 frame.scrollBarMaximum(Qt.Vertical))
            perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
                    round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
            self.scroll_pos = perc
            self.scroll_pos_changed.emit(*perc)
        # Let superclass handle the event
        super().paintEvent(e)

    def mousePressEvent(self, e):
        """Extend QWidget::mousePressEvent().

        This does the following things:
            - Check if a link was clicked with the middle button or Ctrl and
              set the page's open_target attribute accordingly.
            - Emit the editable_elem_selected signal if an editable element was
              clicked.

        Args:
            e: The arrived event.

        Return:
            The superclass return value.
        """
        is_rocker_gesture = (config.get('input', 'rocker-gestures') and
                             e.buttons() == Qt.LeftButton | Qt.RightButton)

        if e.button() in (Qt.XButton1, Qt.XButton2) or is_rocker_gesture:
            self._mousepress_backforward(e)
            super().mousePressEvent(e)
            return
        self._mousepress_insertmode(e)
        self._mousepress_opentarget(e)
        self._ignore_wheel_event = True
        super().mousePressEvent(e)

    def mouseReleaseEvent(self, e):
        """Extend mouseReleaseEvent to enter insert mode if needed."""
        super().mouseReleaseEvent(e)
        # We want to make sure we check the focus element after the WebView is
        # updated completely.
        QTimer.singleShot(0, self.mouserelease_insertmode)

    def contextMenuEvent(self, e):
        """Save a reference to the context menu so we can close it."""
        menu = self.page().createStandardContextMenu()
        self.shutting_down.connect(menu.close)
        modeman.instance(self.win_id).entered.connect(menu.close)
        menu.exec_(e.globalPos())

    def wheelEvent(self, e):
        """Zoom on Ctrl-Mousewheel.

        Args:
            e: The QWheelEvent.
        """
        if self._ignore_wheel_event:
            self._ignore_wheel_event = False
            # See https://github.com/The-Compiler/qutebrowser/issues/395
            return
        if e.modifiers() & Qt.ControlModifier:
            e.accept()
            divider = config.get('input', 'mouse-zoom-divider')
            factor = self.zoomFactor() + e.angleDelta().y() / divider
            if factor < 0:
                return
            perc = int(100 * factor)
            message.info(self.win_id, "Zoom level: {}%".format(perc))
            self._zoom.fuzzyval = perc
            self.setZoomFactor(factor)
            self._default_zoom_changed = True
        else:
            super().wheelEvent(e)