mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-15 00:15:17 +00:00
Merge pull request #3684 from Dokploy/fix/add-punycode
feat(traefik): add support for internationalized domain names (IDN)
This commit is contained in:
@@ -275,3 +275,51 @@ test("CertificateType on websecure entrypoint", async () => {
|
|||||||
|
|
||||||
expect(router.tls?.certResolver).toBe("letsencrypt");
|
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** IDN/Punycode */
|
||||||
|
|
||||||
|
test("Internationalized domain name is converted to punycode", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, host: "тест.рф" },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
// тест.рф in punycode is xn--e1aybc.xn--p1ai
|
||||||
|
expect(router.rule).toContain("Host(`xn--e1aybc.xn--p1ai`)");
|
||||||
|
expect(router.rule).not.toContain("тест.рф");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ASCII domain remains unchanged", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, host: "example.com" },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.rule).toContain("Host(`example.com`)");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Russian Cyrillic label with .ru TLD is converted to punycode", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, host: "сайт.ru" },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
// сайт in punycode is xn--80aswg
|
||||||
|
expect(router.rule).toContain("Host(`xn--80aswg.ru`)");
|
||||||
|
expect(router.rule).not.toContain("сайт");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Subdomain with Russian IDN TLD converts non-ASCII part to punycode", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, host: "app.тест.рф" },
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
|
||||||
|
// app stays ASCII, тест.рф becomes xn--e1aybc.xn--p1ai
|
||||||
|
expect(router.rule).toContain("Host(`app.xn--e1aybc.xn--p1ai`)");
|
||||||
|
expect(router.rule).not.toContain("тест.рф");
|
||||||
|
});
|
||||||
|
|||||||
@@ -104,6 +104,20 @@ export const removeDomain = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an internationalized domain name (IDN) to ASCII punycode format.
|
||||||
|
* Traefik requires domain names in ASCII format, so non-ASCII characters
|
||||||
|
* must be converted (e.g., "тест.рф" → "xn--e1aybc.xn--p1ai").
|
||||||
|
*/
|
||||||
|
const toPunycode = (host: string): string => {
|
||||||
|
try {
|
||||||
|
return new URL(`http://${host}`).hostname;
|
||||||
|
} catch {
|
||||||
|
// If URL parsing fails, return the original host
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const createRouterConfig = async (
|
export const createRouterConfig = async (
|
||||||
app: ApplicationNested,
|
app: ApplicationNested,
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
@@ -114,8 +128,9 @@ export const createRouterConfig = async (
|
|||||||
|
|
||||||
const { host, path, https, uniqueConfigKey, internalPath, stripPath } =
|
const { host, path, https, uniqueConfigKey, internalPath, stripPath } =
|
||||||
domain;
|
domain;
|
||||||
|
const punycodeHost = toPunycode(host);
|
||||||
const routerConfig: HttpRouter = {
|
const routerConfig: HttpRouter = {
|
||||||
rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
rule: `Host(\`${punycodeHost}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
||||||
service: `${appName}-service-${uniqueConfigKey}`,
|
service: `${appName}-service-${uniqueConfigKey}`,
|
||||||
middlewares: [],
|
middlewares: [],
|
||||||
entryPoints: [entryPoint],
|
entryPoints: [entryPoint],
|
||||||
|
|||||||
Reference in New Issue
Block a user