Skip to content

Commit 3cdc83f

Browse files
fix(mobile): harden address-bar navigation (#61)
Co-authored-by: Raul <311655720+raul-clearframe@users.noreply.github.com>
1 parent ac037ed commit 3cdc83f

3 files changed

Lines changed: 83 additions & 11 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { HOME, normalizeUrl } from './navigation';
3+
4+
describe('normalizeUrl', () => {
5+
it('returns home for blank input', () => {
6+
expect(normalizeUrl(' ')).toBe(HOME);
7+
});
8+
9+
it('keeps valid HTTP(S) URLs', () => {
10+
expect(normalizeUrl('https://example.com/path?q=1')).toBe(
11+
'https://example.com/path?q=1',
12+
);
13+
expect(normalizeUrl('http://localhost:3000/health')).toBe(
14+
'http://localhost:3000/health',
15+
);
16+
});
17+
18+
it('promotes a complete domain to HTTPS', () => {
19+
expect(normalizeUrl('docs.example.com/path')).toBe(
20+
'https://docs.example.com/path',
21+
);
22+
});
23+
24+
it('searches ordinary text', () => {
25+
expect(normalizeUrl('privacy first browser')).toBe(
26+
'https://duckduckgo.com/?q=privacy%20first%20browser',
27+
);
28+
});
29+
30+
it('searches unsupported schemes instead of loading them', () => {
31+
expect(normalizeUrl('javascript:alert(1)')).toBe(
32+
'https://duckduckgo.com/?q=javascript%3Aalert(1)',
33+
);
34+
});
35+
36+
it('does not treat domain-looking text with spaces as a URL', () => {
37+
expect(normalizeUrl('example.com malicious suffix')).toBe(
38+
'https://duckduckgo.com/?q=example.com%20malicious%20suffix',
39+
);
40+
});
41+
});

‎apps/mobile/src/lib/navigation.ts‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export const HOME = 'https://tronbrowser.dev';
2+
3+
const DOMAIN_OR_IP =
4+
/^(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|(?:\d{1,3}\.){3}\d{1,3}|localhost)(?::\d{1,5})?(?:[/?#][^\s]*)?$/i;
5+
6+
function searchUrl(query: string): string {
7+
return `https://duckduckgo.com/?q=${encodeURIComponent(query)}`;
8+
}
9+
10+
/**
11+
* Convert address-bar input into a safe, loadable URL.
12+
*
13+
* Only explicit HTTP(S) URLs and complete domain/IP inputs are navigated to.
14+
* Everything else, including unsupported schemes and domain-looking text with
15+
* spaces, becomes a search query instead of reaching the WebView as a URL.
16+
*/
17+
export function normalizeUrl(input: string): string {
18+
const trimmed = input.trim();
19+
if (!trimmed) return HOME;
20+
21+
if (/^https?:\/\//i.test(trimmed)) {
22+
try {
23+
const parsed = new URL(trimmed);
24+
return parsed.protocol === 'http:' || parsed.protocol === 'https:'
25+
? parsed.toString()
26+
: searchUrl(trimmed);
27+
} catch {
28+
return searchUrl(trimmed);
29+
}
30+
}
31+
32+
if (DOMAIN_OR_IP.test(trimmed)) {
33+
try {
34+
return new URL(`https://${trimmed}`).toString();
35+
} catch {
36+
return searchUrl(trimmed);
37+
}
38+
}
39+
40+
return searchUrl(trimmed);
41+
}

‎apps/mobile/src/screens/BrowserScreen.tsx‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
View,
1010
} from 'react-native';
1111
import { WebView } from 'react-native-webview';
12+
import { HOME, normalizeUrl } from '../lib/navigation';
1213
import { theme } from '../theme';
1314

1415
/**
@@ -19,17 +20,6 @@ import { theme } from '../theme';
1920
* Ungoogled Chromium engine (see docs/mobile-architecture.md — the engine ships
2021
* via the native Android build and the Linux-phone desktop build, not Expo).
2122
*/
22-
const HOME = 'https://tronbrowser.dev';
23-
24-
function normalizeUrl(input: string): string {
25-
const trimmed = input.trim();
26-
if (!trimmed) return HOME;
27-
if (/^https?:\/\//i.test(trimmed)) return trimmed;
28-
// A bare domain-looking string → https; otherwise treat as a search query.
29-
if (/^[\w-]+(\.[\w-]+)+/.test(trimmed)) return `https://${trimmed}`;
30-
return `https://duckduckgo.com/?q=${encodeURIComponent(trimmed)}`;
31-
}
32-
3323
export function BrowserScreen() {
3424
const webRef = useRef<WebView>(null);
3525
const [address, setAddress] = useState(HOME);

0 commit comments

Comments
 (0)