Fix #5523: allow %s replacement in subdomains

This commit is contained in:
Oliver Blanthorn 2026-09-07 16:36:42 +02:00
parent c330f7eebd
commit c2e6452a46
No known key found for this signature in database
GPG key ID: 2BB8C36BB504BFF3
3 changed files with 28 additions and 14 deletions

View file

@ -347,6 +347,21 @@ function test_url_graft_path() {
function test_url_query_interpolation() {
let cases = [
["http://example.com/%s000", "a/query", "http://example.com/a%2Fquery000"],
[
"https://%s1.wikipedia.org/wiki/%s2",
"en Tridactyl",
"https://en.wikipedia.org/wiki/Tridactyl",
],
[
"https://%s.wikipedia.org/wiki/Tridactyl",
"en",
"https://en.wikipedia.org/wiki/Tridactyl",
],
[
"https://%s[1:2].wikipedia.org/wiki/%s[2:]",
"en Main Page",
"https://en.wikipedia.org/wiki/Main%20Page",
],
[
// appended to the path
"http://example.com",
@ -384,7 +399,7 @@ function test_url_query_interpolation() {
]
for (let [url, qy, exp_res] of cases) {
let modified = UrlUtil.interpolateSearchItem(new URL(url), qy)
let modified = UrlUtil.interpolateSearchItem(url, qy)
test(`interpolate ${qy} into ${url} --> ${exp_res}`, () =>
expect(modified.href).toEqual(exp_res))
@ -396,7 +411,7 @@ test.each([
["wiki", "https://example.com/wiki/", "an/article"],
])("convert an interpolated %s URL back to arguments", (engine, pattern, query) => {
const searchurls = { [engine]: pattern }
const url = UrlUtil.interpolateSearchItem(new URL(pattern), query).href
const url = UrlUtil.interpolateSearchItem(pattern, query).href
expect(UrlUtil.searchUrlToArgs(url, searchurls)).toEqual(`${engine} ${query}`)
})

View file

@ -441,14 +441,14 @@ export function searchUrlToArgs(
*
* The search item is percent encoded before it is inserted.
*
* @param urlPattern a URL to interpolate/append a query to
* @param urlPattern a URL template to interpolate/append a query to
* @param query a query to interpolate/append into the URL
*
* @return the URL with the query encoded and inserted at the
* relevant point
*/
export function interpolateSearchItem(urlPattern: URL, query: string): URL {
const hasInterpolationPoint = urlPattern.href.includes("%s")
export function interpolateSearchItem(urlPattern: string, query: string): URL {
const hasInterpolationPoint = urlPattern.includes("%s")
let queryWords = query.split(" ")
@ -456,10 +456,10 @@ export function interpolateSearchItem(urlPattern: URL, query: string): URL {
query = encodeURIComponent(query)
queryWords = queryWords.map(w => encodeURIComponent(w))
// replace or append as needed
// Interpolate before parsing so placeholders can appear in the hostname.
if (hasInterpolationPoint) {
const resultingURL = new URL(
urlPattern.href
return new URL(
urlPattern
.replace(/%s[1-9]\d*/g, function (x) {
const index = parseInt(x.slice(2), 10) - 1
if (index >= queryWords.length) {
@ -476,12 +476,11 @@ export function interpolateSearchItem(urlPattern: URL, query: string): URL {
? queryWords.slice(start, l(parseInt(p2, 10)))
: queryWords.slice(start)
return slice.join(" ")
}),
})
.replace("%s", query),
)
return new URL(resultingURL.href.replace("%s", query))
} else {
return new URL(urlPattern.href + query)
return new URL(new URL(urlPattern).href + query)
}
}

View file

@ -389,7 +389,7 @@ export async function queryAndURLwrangler(
const searchurls = config.get("searchurls")
const template = expandRecursively(firstWord, searchurls)
if (template != firstWord) {
const url = UrlUtil.interpolateSearchItem(new URL(template), rest)
const url = UrlUtil.interpolateSearchItem(template, rest)
// firstWord is a searchurl, so let's use that
return url.href
}
@ -445,7 +445,7 @@ export async function queryAndURLwrangler(
if (enginename) {
if (searchurls[enginename]) {
const url = UrlUtil.interpolateSearchItem(
new URL(searchurls[enginename]),
searchurls[enginename],
queryString,
)
return url.href