parent
046148fa8f
commit
147eb058b9
|
|
@ -2638,6 +2638,7 @@ Type: <<types,String>>
|
|||
Valid values:
|
||||
|
||||
* +ask+: Ask how to proceed for every certificate error (unless non-overridable due to HSTS).
|
||||
* +ask-block-thirdparty+: Ask how to proceed for normal page loads, but silently block resource loads.
|
||||
* +block+: Automatically block loading on certificate errors.
|
||||
* +load-insecurely+: Force loading pages despite certificate errors. This is *insecure* and should be avoided. Instead of using this, consider fixing the underlying issue or importing a self-signed certificate via `certutil` (or Chromium) instead.
|
||||
|
||||
|
|
|
|||
|
|
@ -156,34 +156,61 @@ def javascript_log_message(level, source, line, msg):
|
|||
|
||||
|
||||
def ignore_certificate_error(
|
||||
url: QUrl,
|
||||
*,
|
||||
request_url: QUrl,
|
||||
first_party_url: QUrl,
|
||||
error: usertypes.AbstractCertificateErrorWrapper,
|
||||
abort_on: Iterable[pyqtBoundSignal],
|
||||
) -> bool:
|
||||
"""Display a certificate error question.
|
||||
|
||||
Args:
|
||||
url: The URL the errors happened in
|
||||
errors: A single error.
|
||||
request_url: The URL of the request where the errors happened.
|
||||
first_party_url: The URL of the page we're visiting. Might be an invalid QUrl.
|
||||
error: A single error.
|
||||
abort_on: Signals aborting a question.
|
||||
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
conf = config.instance.get('content.tls.certificate_errors', url=url)
|
||||
conf = config.instance.get('content.tls.certificate_errors', url=request_url)
|
||||
log.network.debug(f"Certificate error {error!r}, config {conf}")
|
||||
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if conf == 'ask':
|
||||
# We get the first party URL with a heuristic - with HTTP -> HTTPS redirects, the
|
||||
# scheme might not match.
|
||||
is_resource = (
|
||||
first_party_url.isValid() and
|
||||
not request_url.matches(first_party_url, QUrl.RemoveScheme))
|
||||
|
||||
if conf == 'ask' or conf == 'ask-block-thirdparty' and not is_resource:
|
||||
err_template = jinja.environment.from_string("""
|
||||
<p>Error while loading <b>{{url.toDisplayString()}}</b>:</p>
|
||||
{% if is_resource %}
|
||||
<p>
|
||||
Error while loading resource <b>{{request_url.toDisplayString()}}</b><br/>
|
||||
on page <b>{{first_party_url.toDisplayString()}}</b>:
|
||||
</p>
|
||||
{% else %}
|
||||
<p>Error while loading page <b>{{request_url.toDisplayString()}}</b>:</p>
|
||||
{% endif %}
|
||||
|
||||
{{error.html()|safe}}
|
||||
""".strip())
|
||||
msg = err_template.render(url=url, error=error)
|
||||
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
{% if is_resource %}
|
||||
<p><i>Consider reporting this to the website operator, or set
|
||||
<tt>content.tls.certificate_errors</tt> to <tt>ask-block-thirdparty</tt> to
|
||||
always block invalid resource loads.</i></p>
|
||||
{% endif %}
|
||||
""".strip())
|
||||
msg = err_template.render(
|
||||
request_url=request_url,
|
||||
first_party_url=first_party_url,
|
||||
is_resource=is_resource,
|
||||
error=error,
|
||||
)
|
||||
|
||||
urlstr = request_url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
ignore = message.ask(title="Certificate error - continue?", text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on, url=urlstr)
|
||||
|
|
@ -196,7 +223,13 @@ def ignore_certificate_error(
|
|||
return True
|
||||
elif conf == 'block':
|
||||
return False
|
||||
raise utils.Unreachable(conf)
|
||||
elif conf == 'ask-block-thirdparty' and is_resource:
|
||||
log.network.error(
|
||||
f"Certificate error in resource load: {error}\n"
|
||||
f" request URL: {request_url.toDisplayString()}\n"
|
||||
f" first party URL: {first_party_url.toDisplayString()}")
|
||||
return False
|
||||
raise utils.Unreachable(conf, is_resource)
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
|
|
|
|||
|
|
@ -1516,11 +1516,22 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
url = error.url()
|
||||
self._insecure_hosts.add(url.host())
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-92009
|
||||
# self.url() is not available yet and the requested URL might not match the URL
|
||||
# we get from the error - so we just apply a heuristic here.
|
||||
assert self.data.last_navigation is not None
|
||||
first_party_url = self.data.last_navigation.url
|
||||
|
||||
log.network.debug("Certificate error: {}".format(error))
|
||||
log.network.debug("First party URL: {}".format(first_party_url))
|
||||
|
||||
if error.is_overridable():
|
||||
error.ignore = shared.ignore_certificate_error(
|
||||
url, error, abort_on=[self.abort_questions])
|
||||
request_url=url,
|
||||
first_party_url=first_party_url,
|
||||
error=error,
|
||||
abort_on=[self.abort_questions],
|
||||
)
|
||||
else:
|
||||
log.network.error("Non-overridable certificate error: "
|
||||
"{}".format(error))
|
||||
|
|
@ -1544,12 +1555,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
# We can't really know when to show an error page, as the error might
|
||||
# have happened when loading some resource.
|
||||
# However, self.url() is not available yet and the requested URL
|
||||
# might not match the URL we get from the error - so we just apply a
|
||||
# heuristic here.
|
||||
assert self.data.last_navigation is not None
|
||||
if (show_non_overr_cert_error and
|
||||
url.matches(self.data.last_navigation.url, QUrl.RemoveScheme)):
|
||||
is_resource = (
|
||||
first_party_url.isValid() and
|
||||
url.matches(first_party_url, QUrl.RemoveScheme))
|
||||
if show_non_overr_cert_error and is_resource:
|
||||
self._show_error_page(url, str(error))
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
|||
|
|
@ -214,6 +214,25 @@ class NetworkManager(QNetworkAccessManager):
|
|||
abort_on.append(tab.load_started)
|
||||
return abort_on
|
||||
|
||||
def _get_tab(self):
|
||||
"""Get the tab this NAM is associated with.
|
||||
|
||||
Return:
|
||||
The tab if available, None otherwise.
|
||||
"""
|
||||
# There are some scenarios where we can't figure out current_url:
|
||||
# - There's a generic NetworkManager, e.g. for downloads
|
||||
# - The download was in a tab which is now closed.
|
||||
if self._tab_id is None:
|
||||
return
|
||||
|
||||
assert self._win_id is not None
|
||||
try:
|
||||
return objreg.get('tab', scope='tab', window=self._win_id, tab=self._tab_id)
|
||||
except KeyError:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/889
|
||||
return None
|
||||
|
||||
def shutdown(self):
|
||||
"""Abort all running requests."""
|
||||
self.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
|
||||
|
|
@ -254,7 +273,16 @@ class NetworkManager(QNetworkAccessManager):
|
|||
return
|
||||
|
||||
abort_on = self._get_abort_signals(reply)
|
||||
ignore = shared.ignore_certificate_error(reply.url(), errors, abort_on=abort_on)
|
||||
|
||||
tab = self._get_tab()
|
||||
first_party_url = QUrl() if tab is None else tab.data.last_navigation.url
|
||||
|
||||
ignore = shared.ignore_certificate_error(
|
||||
request_url=reply.url(),
|
||||
first_party_url=first_party_url,
|
||||
error=errors,
|
||||
abort_on=abort_on,
|
||||
)
|
||||
if ignore:
|
||||
reply.ignoreSslErrors()
|
||||
if host_tpl is not None:
|
||||
|
|
@ -387,22 +415,14 @@ class NetworkManager(QNetworkAccessManager):
|
|||
for header, value in shared.custom_headers(url=req.url()):
|
||||
req.setRawHeader(header, value)
|
||||
|
||||
# There are some scenarios where we can't figure out current_url:
|
||||
# - There's a generic NetworkManager, e.g. for downloads
|
||||
# - The download was in a tab which is now closed.
|
||||
tab = self._get_tab()
|
||||
current_url = QUrl()
|
||||
|
||||
if self._tab_id is not None:
|
||||
assert self._win_id is not None
|
||||
if tab is not None:
|
||||
try:
|
||||
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
current_url = tab.url()
|
||||
except (KeyError, RuntimeError):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/889
|
||||
# Catching RuntimeError because we could be in the middle of
|
||||
# the webpage shutdown here.
|
||||
current_url = QUrl()
|
||||
except RuntimeError:
|
||||
# We could be in the middle of the webpage shutdown here.
|
||||
pass
|
||||
|
||||
request = interceptors.Request(first_party_url=current_url,
|
||||
request_url=req.url())
|
||||
|
|
|
|||
|
|
@ -960,6 +960,8 @@ content.tls.certificate_errors:
|
|||
valid_values:
|
||||
- ask: Ask how to proceed for every certificate error (unless non-overridable due
|
||||
to HSTS).
|
||||
- ask-block-thirdparty: Ask how to proceed for normal page loads, but silently
|
||||
block resource loads.
|
||||
- block: Automatically block loading on certificate errors.
|
||||
- load-insecurely: Force loading pages despite certificate errors. This is
|
||||
*insecure* and should be avoided. Instead of using this, consider fixing the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const elem = document.getElementById('text');
|
||||
elem.textContent = 'Script loaded';
|
||||
console.log('Script loaded');
|
||||
})
|
||||
|
|
@ -202,6 +202,44 @@ Feature: Prompts
|
|||
And I run :mode-leave
|
||||
Then a SSL error page should be shown
|
||||
|
||||
Scenario: SSL error with content.tls.certificate_errors = ask-block-thirdparty -> yes
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask-block-thirdparty
|
||||
And I load an SSL page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept yes
|
||||
And I wait until the SSL page finished loading
|
||||
Then the page should contain the plaintext "Hello World via SSL!"
|
||||
|
||||
Scenario: SSL resource error with content.tls.certificate_errors = ask -> yes
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL resource page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept yes
|
||||
And I wait until the SSL resource page finished loading
|
||||
Then the javascript message "Script loaded" should be logged
|
||||
And the page should contain the plaintext "Script loaded"
|
||||
|
||||
Scenario: SSL resource error with content.tls.certificate_errors = ask -> no
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask
|
||||
And I load an SSL resource page
|
||||
And I wait for a prompt
|
||||
And I run :prompt-accept no
|
||||
And I wait until the SSL resource page finished loading
|
||||
Then the javascript message "Script loaded" should not be logged
|
||||
And the page should contain the plaintext "Script not loaded"
|
||||
|
||||
Scenario: SSL resource error with content.tls.certificate_errors = ask-block-thirdparty
|
||||
When I clear SSL errors
|
||||
And I set content.tls.certificate_errors to ask-block-thirdparty
|
||||
And I load an SSL resource page
|
||||
And I wait until the SSL resource page finished loading
|
||||
Then "Certificate error in resource load: *" should be logged
|
||||
And the javascript message "Script loaded" should not be logged
|
||||
And the page should contain the plaintext "Script not loaded"
|
||||
|
||||
# Geolocation
|
||||
|
||||
Scenario: Always rejecting geolocation
|
||||
|
|
|
|||
|
|
@ -36,6 +36,17 @@ def wait_ssl_page_finished_loading(quteproc, ssl_server):
|
|||
load_status='warn')
|
||||
|
||||
|
||||
@bdd.when("I load an SSL resource page")
|
||||
def load_ssl_resource_page(quteproc, server, ssl_server):
|
||||
# We don't wait here as we can get an SSL question.
|
||||
quteproc.open_path(f'https-script/{ssl_server.port}', port=server.port, wait=False)
|
||||
|
||||
|
||||
@bdd.when("I wait until the SSL resource page finished loading")
|
||||
def wait_ssl_resource_page_finished_loading(quteproc, server, ssl_server):
|
||||
quteproc.wait_for_load_finished(f'https-script/{ssl_server.port}', port=server.port)
|
||||
|
||||
|
||||
@bdd.when("I wait for a prompt")
|
||||
def wait_for_prompt(quteproc):
|
||||
quteproc.wait_for(message='Asking question *')
|
||||
|
|
|
|||
|
|
@ -261,6 +261,12 @@ def headers_link(port):
|
|||
return flask.render_template('headers-link.html', port=port)
|
||||
|
||||
|
||||
@app.route('/https-script/<int:port>')
|
||||
def https_script(port):
|
||||
"""Get a script loaded via HTTPS."""
|
||||
return flask.render_template('https-script.html', port=port)
|
||||
|
||||
|
||||
@app.route('/response-headers')
|
||||
def response_headers():
|
||||
"""Return a set of response headers from the query string."""
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ def hello_world():
|
|||
return "Hello World via SSL!"
|
||||
|
||||
|
||||
@app.route('/data/<path:path>')
|
||||
def send_data(path):
|
||||
return webserver_sub.send_data(path)
|
||||
|
||||
|
||||
@app.route('/favicon.ico')
|
||||
def favicon():
|
||||
return webserver_sub.favicon()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>HTTPS script</title>
|
||||
<script src="https://localhost:{{ port }}/data/prompt/script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="text">Script not loaded.</p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue