1
>
/*---------------------------------------------------------------------------------------------
browserSearch.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
/*
7
>
* NOTE: {@link resolveAddressBarInputType} is a deliberate, self-contained
8
>
* APPROXIMATION of Chromium's omnibox parser (`AutocompleteInput::Parse`), not
9
>
* a faithful port. It intentionally diverges in places — most notably it uses a
10
>
* lightweight "any 2+ letter last label is a TLD" heuristic instead of a Public
11
>
* Suffix List lookup — to avoid maintaining large data tables for an address
12
>
* bar. Please treat the unit tests in `browserSearch.test.ts` as the
13
>
* specification: do not "fix" this to byte-match Chromium. If Chromium's
14
>
* behavior changes in a way we care about, re-sync deliberately and update the
15
>
* tests.
16
>
*/
17
>
18
>
import { localize } from '../../../../nls.js';
19
>
20
>
/**
21
>
* Identifier of the integrated browser address bar search engine.
22
>
*/
23
>
export enum BrowserSearchEngineId {
24
>
Bing = 'bing',
25
>
Google = 'google',
26
>
Yahoo = 'yahoo',
27
>
DuckDuckGo = 'duckduckgo',
28
>
}
29
>
30
>
export const BrowserSearchEngineSettingId =
31
>
'workbench.browser.searchEngine';
32
>
33
>
/**
34
>
* Value of {@link BrowserSearchEngineSettingId} when no search engine is
35
>
* selected (address bar search disabled). Selecting any other value both
36
>
* enables search and picks the engine.
37
>
*/
38
>
export const BROWSER_SEARCH_NONE = 'none';
39
>
40
>
/**
41
>
* The address bar search setting value: either `'none'` (search disabled) or a
42
>
* specific {@link BrowserSearchEngineId}.
43
>
*/
44
>
export type BrowserSearchEngineValue = BrowserSearchEngineId | typeof BROWSER_SEARCH_NONE;
45
>
46
>
/**
47
>
* A search engine that can be selected as the integrated browser's default.
48
>
*/
49
>
export interface IBrowserSearchEngine {
50
>
readonly id: BrowserSearchEngineId;
51
>
/** Human-readable label shown in the settings UI. */
52
>
readonly label: string;
53
>
/**
54
>
* Build a search URL for the given query string. The query is the raw
55
>
* (already trimmed) user input; implementations are responsible for
56
>
* URL-encoding it.
57
>
*/
58
>
buildSearchUrl(query: string): string;
59
>
}
60
>
61
>
/**
62
>
* Encode a search query for use in a search-engine URL. Matches the encoding
63
>
* used by popular browsers: `encodeURIComponent` then replace `%20` with `+`.
64
>
*/
65
function encodeQuery(query: string): string {
66
return encodeURIComponent(query).replace(/%20/g, '+');
67
}
69
>
/**
70
>
* Ordered list of supported search engines.
71
>
*/
72
>
export const BROWSER_SEARCH_ENGINES: readonly IBrowserSearchEngine[] = [
73
>
{
74
>
id: BrowserSearchEngineId.Bing,
75
>
label: localize('browser.search.engine.bing', "Bing"),
76
>
buildSearchUrl: (q) => `https://www.bing.com/search?q=${encodeQuery(q)}`,
77
>
},
78
>
{
79
>
id: BrowserSearchEngineId.Google,
80
>
label: localize('browser.search.engine.google', "Google"),
81
>
buildSearchUrl: (q) => `https://www.google.com/search?q=${encodeQuery(q)}`,
82
>
},
83
>
{
84
>
id: BrowserSearchEngineId.Yahoo,
85
>
label: localize('browser.search.engine.yahoo', "Yahoo!"),
86
>
buildSearchUrl: (q) =>
87
`https://search.yahoo.com/search?p=${encodeQuery(q)}`,
89
>
{
90
>
id: BrowserSearchEngineId.DuckDuckGo,
91
>
label: localize('browser.search.engine.duckduckgo', "DuckDuckGo"),
92
>
buildSearchUrl: (q) => `https://duckduckgo.com/?q=${encodeQuery(q)}`,
93
>
},
94
>
];
95
>
96
>
/**
97
>
* Classification of an address bar input. Mirrors the four non-deprecated
98
>
* values of Chromium's `metrics::OmniboxInputType`:
99
>
* - `'empty'`: input is whitespace-only.
100
>
* - `'url'`: input is recognized as a navigable URL.
101
>
* - `'query'`: input is recognized as a search query (or an invalid URL that
102
>
* can only reasonably be treated as a query).
103
>
* - `'unknown'`: input is ambiguous — could be a URL (e.g. an intranet host
104
>
* or new TLD) or a search; callers should default to search but may offer
105
>
* a "did you mean to navigate?" affordance.
106
>
*/
107
>
export type AddressBarInputKind = 'empty' | 'url' | 'query' | 'unknown';
108
>
109
>
/**
110
>
* Known URL schemes other than http/https and javascript. Anything in this
111
>
* set is classified as URL when typed as a leading `scheme:` prefix.
112
>
*/
113
>
const KNOWN_URL_SCHEMES = new Set([
114
>
'file',
115
>
'ftp',
116
>
'ftps',
117
>
'about',
118
>
'data',
119
>
'view-source',
120
>
'mailto',
121
>
'chrome',
122
>
'edge',
123
>
'vscode',
124
>
'vscode-insiders',
125
>
]);
126
>
127
>
/**
128
>
* All schemes that we recognize as actual URL schemes (used to disambiguate
129
>
* `scheme:operand` from `host:port` — e.g. `localhost:3000` looks like a
130
>
* scheme syntactically but `localhost` is not a known scheme).
131
>
*/
132
>
const ALL_KNOWN_SCHEMES = new Set<string>([...KNOWN_URL_SCHEMES, 'http', 'https', 'javascript']);
133
>
134
>
/**
135
>
* Special-cased TLDs from RFC 6761 / RFC 6762 / ICANN that are treated as
136
>
* known only when a subdomain is present. `.invalid` is reserved as
137
>
* non-navigable.
138
>
*/
139
>
const SUBDOMAIN_REQUIRED_TLDS = new Set(['example', 'test', 'local', 'internal']);
140
>
141
>
const SCHEME_REGEX = /^([a-z][a-z0-9+\-.]*):/i;
142
>
const JAVASCRIPT_QUERY_REGEX = /^javascript:[^;=().\"]*$/i;
143
>
const USERINFO_WITH_PASSWORD_REGEX = /^[^\s:@/?#]+:[^\s@/?#]+@/;
144
>
const HOST_CHARS_REGEX = /^[a-zA-Z0-9\-._~%]+$/;
145
>
const PORT_REGEX = /^\d+$/;
146
>
const IPV4_REGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
147
>
148
>
/**
149
>
* Canonicalize a hostname using the platform's URL parser. Converts
150
>
* non-ASCII (IDN) hostnames to their punycode (`xn--`) form, and validates
151
>
* and canonicalizes bracketed IPv6 literals (e.g. `[2001:0db8::0001]` →
152
>
* `[2001:db8::1]`). Returns `undefined` if the host can't be canonicalized.
153
>
* ASCII non-bracket hosts are returned unchanged (validation happens via
154
>
* `HOST_CHARS_REGEX` at the call site).
155
>
*/
156
function toAsciiHost(host: string): string | undefined {
157
const needsUrlParse = host.startsWith('[') || !/^[\x00-\x7F]*$/.test(host);