pick http for .onion by default
This commit is contained in:
@@ -821,6 +821,13 @@
|
|||||||
"enforceOnStartup": true,
|
"enforceOnStartup": true,
|
||||||
"locked": true
|
"locked": true
|
||||||
},
|
},
|
||||||
|
"dom.security.https_only_mode.upgrade_onion": {
|
||||||
|
"value": false,
|
||||||
|
"title": "Keep .onion sites on HTTP",
|
||||||
|
"description": "Exempts .onion hosts from HTTPS-Only/HTTPS-First upgrades. Onion services are reached over an encrypted, self-authenticating Tor circuit, so HTTP carries no clearnet exposure and most onion sites are HTTP-only.",
|
||||||
|
"enforceOnStartup": true,
|
||||||
|
"locked": true
|
||||||
|
},
|
||||||
"security.mixed_content.upgrade_display_content": {
|
"security.mixed_content.upgrade_display_content": {
|
||||||
"value": true,
|
"value": true,
|
||||||
"title": "Upgrade mixed display content to HTTPS",
|
"title": "Upgrade mixed display content to HTTPS",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ enum NavigationReason {
|
|||||||
explicitScheme,
|
explicitScheme,
|
||||||
schemelessHost,
|
schemelessHost,
|
||||||
localhost,
|
localhost,
|
||||||
|
onionHost,
|
||||||
ipLiteral,
|
ipLiteral,
|
||||||
aboutLike,
|
aboutLike,
|
||||||
}
|
}
|
||||||
@@ -145,6 +146,13 @@ InputClassification classifyAddressBarInput(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOnionHost(schemelessUri.host)) {
|
||||||
|
return InputClassification.navigate(
|
||||||
|
schemelessUri,
|
||||||
|
NavigationReason.onionHost,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (InternetAddress.tryParse(schemelessUri.host) != null) {
|
if (InternetAddress.tryParse(schemelessUri.host) != null) {
|
||||||
return InputClassification.navigate(
|
return InputClassification.navigate(
|
||||||
schemelessUri,
|
schemelessUri,
|
||||||
|
|||||||
@@ -114,6 +114,12 @@ bool isValidHostCandidate(String hostCandidate) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isOnionHost(String host) {
|
||||||
|
// RFC 7686: ".onion" must be the final label, never a substring. The leading
|
||||||
|
// dot requirement rejects bare "onion", "notonion.com" and "onion.evil.com".
|
||||||
|
return host.toLowerCase().endsWith('.onion');
|
||||||
|
}
|
||||||
|
|
||||||
bool looksLikeHostExpression(String input) {
|
bool looksLikeHostExpression(String input) {
|
||||||
return input.contains('.') ||
|
return input.contains('.') ||
|
||||||
input.contains(':') ||
|
input.contains(':') ||
|
||||||
@@ -183,7 +189,13 @@ Uri? parseSchemelessWebHost(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final scheme = probeUri.host.toLowerCase() == 'localhost' ? 'http' : 'https';
|
// Onion services are self-authenticating and reached over an encrypted Tor
|
||||||
|
// circuit, so http carries no clearnet exposure; default them to http like
|
||||||
|
// localhost (see Tor Browser's HTTPS-Only exemption for .onion).
|
||||||
|
final lowerHost = probeUri.host.toLowerCase();
|
||||||
|
final scheme = (lowerHost == 'localhost' || isOnionHost(lowerHost))
|
||||||
|
? 'http'
|
||||||
|
: 'https';
|
||||||
if (allowedSchemes != null && !allowedSchemes.contains(scheme)) {
|
if (allowedSchemes != null && !allowedSchemes.contains(scheme)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,49 @@ void main() {
|
|||||||
expect(navigation.uri.toString(), 'https://weblibre.eu');
|
expect(navigation.uri.toString(), 'https://weblibre.eu');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('navigates onion host over http', () {
|
||||||
|
final result = classifyAddressBarInput(
|
||||||
|
'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/path',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isA<NavigateInputClassification>());
|
||||||
|
final navigation = result as NavigateInputClassification;
|
||||||
|
expect(navigation.reason, NavigationReason.onionHost);
|
||||||
|
expect(
|
||||||
|
navigation.uri.toString(),
|
||||||
|
'http://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/path',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps explicit https on onion host', () {
|
||||||
|
final result = classifyAddressBarInput(
|
||||||
|
'https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isA<NavigateInputClassification>());
|
||||||
|
final navigation = result as NavigateInputClassification;
|
||||||
|
expect(navigation.reason, NavigationReason.explicitScheme);
|
||||||
|
expect(navigation.uri.scheme, 'https');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not http-default a non-onion lookalike host', () {
|
||||||
|
final result = classifyAddressBarInput('notonion.com');
|
||||||
|
|
||||||
|
expect(result, isA<NavigateInputClassification>());
|
||||||
|
final navigation = result as NavigateInputClassification;
|
||||||
|
expect(navigation.reason, NavigationReason.schemelessHost);
|
||||||
|
expect(navigation.uri.toString(), 'https://notonion.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not http-default onion as a non-final label', () {
|
||||||
|
final result = classifyAddressBarInput('onion.example.com');
|
||||||
|
|
||||||
|
expect(result, isA<NavigateInputClassification>());
|
||||||
|
final navigation = result as NavigateInputClassification;
|
||||||
|
expect(navigation.reason, NavigationReason.schemelessHost);
|
||||||
|
expect(navigation.uri.toString(), 'https://onion.example.com');
|
||||||
|
});
|
||||||
|
|
||||||
test('navigates localhost over http', () {
|
test('navigates localhost over http', () {
|
||||||
final result = classifyAddressBarInput('localhost:8080');
|
final result = classifyAddressBarInput('localhost:8080');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user