115 lines
4.2 KiB
Python
115 lines
4.2 KiB
Python
"""Regression tests for #4571 insecure-origin microphone messaging.
|
|
|
|
Browser speech/microphone APIs can report permission-style errors when the real
|
|
problem is that the page was opened over insecure HTTP from a non-local origin.
|
|
The UI must explain the HTTPS/localhost requirement instead of only saying the
|
|
browser permission was denied.
|
|
"""
|
|
from pathlib import Path
|
|
import re
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
|
|
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def _locale_count() -> int:
|
|
return len(re.findall(r"^ ['\"]?[\w-]+['\"]?: \{", I18N_JS, re.MULTILINE))
|
|
|
|
|
|
def _slice_between(src: str, start_marker: str, end_marker: str) -> str:
|
|
start = src.index(start_marker)
|
|
end = src.index(end_marker, start)
|
|
return src[start:end]
|
|
|
|
|
|
def test_i18n_key_exists_in_every_locale_and_names_https_localhost():
|
|
assert I18N_JS.count("mic_insecure_origin") == _locale_count()
|
|
|
|
english = re.search(r"mic_insecure_origin: '([^']+)'", I18N_JS)
|
|
assert english, "English mic_insecure_origin string must exist"
|
|
value = english.group(1).lower()
|
|
assert "https" in value
|
|
assert "localhost" in value
|
|
assert "microphone" in value
|
|
|
|
|
|
def test_secure_context_and_localhost_loopback_are_explicitly_preserved():
|
|
assert "function _micOriginNeedsSecureContext()" in BOOT_JS
|
|
assert "window.isSecureContext===true" in BOOT_JS
|
|
assert "protocol==='http:'" in BOOT_JS
|
|
assert "!_micIsLocalhostOrLoopback(loc.hostname)" in BOOT_JS
|
|
|
|
localhost_helper = _slice_between(
|
|
BOOT_JS,
|
|
"function _micIsLocalhostOrLoopback(hostname)",
|
|
"\n}\n\nfunction _micOriginNeedsSecureContext",
|
|
)
|
|
assert "host==='localhost'" in localhost_helper
|
|
assert "host.endsWith('.localhost')" in localhost_helper
|
|
assert "host==='::1'" in localhost_helper
|
|
assert "/^127\\./.test(host)" in localhost_helper
|
|
|
|
|
|
def test_permission_style_speech_errors_use_insecure_origin_key_only_on_that_branch():
|
|
helper = _slice_between(
|
|
BOOT_JS,
|
|
"function _micToastKeyForRecognitionError(error)",
|
|
"\n}\n\n(function(){",
|
|
)
|
|
for error in ("not-allowed", "service-not-allowed", "audio-capture"):
|
|
assert error in helper
|
|
assert "_micOriginNeedsSecureContext()" in helper
|
|
assert "return 'mic_insecure_origin';" in helper
|
|
assert "'audio-capture':'mic_denied'" not in helper
|
|
assert "'network':'mic_network'" in helper
|
|
assert "'no-speech':'mic_no_speech'" in helper
|
|
|
|
|
|
def test_dictation_preflights_insecure_origin_before_speech_or_media_capture():
|
|
body = _slice_between(
|
|
BOOT_JS,
|
|
"async function _startMicCapture(holdRequired=false){",
|
|
"\n\n async function _toggleMicCapture(){",
|
|
)
|
|
insecure_idx = body.index("_micOriginNeedsSecureContext()")
|
|
speech_idx = body.index("recognition.start()")
|
|
media_idx = body.index("navigator.mediaDevices.getUserMedia")
|
|
|
|
assert insecure_idx < speech_idx
|
|
assert insecure_idx < media_idx
|
|
assert "showToast(t('mic_insecure_origin'))" in body
|
|
|
|
|
|
def test_dictation_speechrecognition_errors_route_through_shared_helper():
|
|
body = _slice_between(
|
|
BOOT_JS,
|
|
"sr.onerror=(event)=>{",
|
|
"\n return sr;",
|
|
)
|
|
assert "_micToastKeyForRecognitionError(event.error)" in body
|
|
assert "messageKey?t(messageKey):t('mic_error')+event.error" in body
|
|
|
|
|
|
def test_voice_mode_preflights_and_routes_permission_errors_through_shared_helper():
|
|
activate_body = _slice_between(
|
|
BOOT_JS,
|
|
"function _activate(){",
|
|
"\n function _deactivate(){",
|
|
)
|
|
assert "_micOriginNeedsSecureContext()" in activate_body
|
|
assert "showToast(t('mic_insecure_origin'))" in activate_body
|
|
assert "_voiceModeActive=true" in activate_body
|
|
assert activate_body.index("_micOriginNeedsSecureContext()") < activate_body.index("_voiceModeActive=true")
|
|
|
|
start_body = _slice_between(
|
|
BOOT_JS,
|
|
"function _startListening(){",
|
|
"\n function _voiceModeSend(){",
|
|
)
|
|
assert "_micOriginNeedsSecureContext()" in start_body
|
|
assert "showToast(t('mic_insecure_origin'))" in start_body
|
|
assert "_micToastKeyForRecognitionError(event.error)" in start_body
|
|
assert "messageKey?t(messageKey):t('mic_error')+event.error" in start_body
|