From 5d0081a3b980693d745e802616a95786f1d2664a Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 29 May 2026 17:04:30 +0200 Subject: [PATCH] fix(qr): hide QR for post-quantum links on client QR page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening the client sublinks/QR modal crashed when a link used post-quantum keys (ML-DSA-65 / ML-KEM-768): the encoded URL exceeds the antd QRCode capacity and the component throws. The client QR modal rendered the QRCode unconditionally, so it took down the page. The names don't appear verbatim in a share link — mldsa65Verify rides inside pqv= and ML-KEM-768 inside encryption=mlkem768x25519plus. The QR modal and inbound QR modal used a literal-substring guard that missed those encoded forms, leaving the QR (and the crash) in place. Consolidate detection into a single isPostQuantumLink() helper in inbound-link.ts and reuse it across the client QR, inbound QR, client info, and sub surfaces. The copy/download link still works; only the QR image is suppressed for oversized post-quantum links. Closes #4656 --- frontend/src/lib/xray/inbound-link.ts | 7 +++++++ frontend/src/pages/clients/ClientInfoModal.tsx | 15 +-------------- frontend/src/pages/clients/ClientQrModal.tsx | 9 ++++++++- frontend/src/pages/inbounds/QrCodeModal.tsx | 3 ++- frontend/src/pages/sub/SubPage.tsx | 14 +------------- 5 files changed, 19 insertions(+), 29 deletions(-) diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index 7a0fae8a..a30cf81f 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -944,3 +944,10 @@ export function genWireguardConfigs(input: GenWireguardFanoutInput): string { })) .join('\r\n'); } + +export function isPostQuantumLink(link: string): boolean { + if (/[?&]pqv=/.test(link)) return true; + if (link.includes('mlkem768') || link.includes('mldsa65')) return true; + if (link.includes('ML-KEM-768')) return true; + return false; +} diff --git a/frontend/src/pages/clients/ClientInfoModal.tsx b/frontend/src/pages/clients/ClientInfoModal.tsx index fdd20b5a..b0049bef 100644 --- a/frontend/src/pages/clients/ClientInfoModal.tsx +++ b/frontend/src/pages/clients/ClientInfoModal.tsx @@ -6,6 +6,7 @@ import { CopyOutlined, QrcodeOutlined } from '@ant-design/icons'; import { ClipboardManager, HttpUtil, IntlUtil, SizeFormatter } from '@/utils'; import { useDatepicker } from '@/hooks/useDatepicker'; import type { ClientRecord, InboundOption } from '@/hooks/useClients'; +import { isPostQuantumLink } from '@/lib/xray/inbound-link'; import QrPanel from '@/pages/inbounds/QrPanel'; import './ClientInfoModal.css'; @@ -33,20 +34,6 @@ const INBOUND_PROTOCOL_COLORS: Record = { const INBOUND_CHIP_LIMIT = 1; -// Post-quantum keys blow up the encoded URL past what a single QR can -// hold. In VLESS share links the algorithm names don't appear as plain -// text — they ride inside query params: -// - mldsa65Verify becomes `pqv=` (sub/subService.go:841) -// - ML-KEM-768 becomes `encryption=mlkem768x25519plus.<...>` -// We also keep the literal substrings so configs that DO embed them -// directly (e.g. wireguard config text) still match. -function isPostQuantumLink(link: string): boolean { - if (/[?&]pqv=/.test(link)) return true; - if (link.includes('mlkem768') || link.includes('mldsa65')) return true; - if (link.includes('ML-KEM-768')) return true; - return false; -} - // 3x-ui's genRemark concatenates inbound remark + client email (and an // optional extra) using a configurable separator. The email half is // redundant in the row title — the modal already names the client by diff --git a/frontend/src/pages/clients/ClientQrModal.tsx b/frontend/src/pages/clients/ClientQrModal.tsx index 6263414a..14f2c950 100644 --- a/frontend/src/pages/clients/ClientQrModal.tsx +++ b/frontend/src/pages/clients/ClientQrModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Collapse, Modal, Spin } from 'antd'; import { HttpUtil } from '@/utils'; +import { isPostQuantumLink } from '@/lib/xray/inbound-link'; import QrPanel from '@/pages/inbounds/QrPanel'; import type { ClientRecord } from '@/hooks/useClients'; @@ -93,7 +94,13 @@ export default function ClientQrModal({ out.push({ key: `l${idx}`, label: `${t('pages.clients.link')} ${idx + 1}`, - children: , + children: ( + + ), }); }); return out; diff --git a/frontend/src/pages/inbounds/QrCodeModal.tsx b/frontend/src/pages/inbounds/QrCodeModal.tsx index edfb2e71..3894d543 100644 --- a/frontend/src/pages/inbounds/QrCodeModal.tsx +++ b/frontend/src/pages/inbounds/QrCodeModal.tsx @@ -8,6 +8,7 @@ import { genAllLinks, genWireguardConfigs, genWireguardLinks, + isPostQuantumLink, } from '@/lib/xray/inbound-link'; import { inboundFromDb, type DbInboundLike } from '@/lib/xray/inbound-from-db'; import QrPanel from './QrPanel'; @@ -140,7 +141,7 @@ export default function QrCodeModal({ value={item.value} remark={item.header} downloadName={item.downloadName || ''} - showQr={!item.value.includes('mldsa65') && !item.value.includes('ML-KEM-768')} + showQr={!isPostQuantumLink(item.value)} /> ), })), diff --git a/frontend/src/pages/sub/SubPage.tsx b/frontend/src/pages/sub/SubPage.tsx index c99b7cff..10ff9865 100644 --- a/frontend/src/pages/sub/SubPage.tsx +++ b/frontend/src/pages/sub/SubPage.tsx @@ -31,6 +31,7 @@ import { } from '@ant-design/icons'; import { ClipboardManager, IntlUtil, LanguageManager } from '@/utils'; +import { isPostQuantumLink } from '@/lib/xray/inbound-link'; import { setMessageInstance } from '@/utils/messageBus'; import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme'; import SubUsageSummary from './SubUsageSummary'; @@ -90,19 +91,6 @@ function trimEmail(remark: string, email: string): string { .trim(); } -// Post-quantum keys blow up the encoded URL past what a single QR can -// hold. The algorithm names don't appear as plain text in the URL — -// they ride inside query params: mldsa65Verify → `pqv=`, -// ML-KEM-768 → `encryption=mlkem768x25519plus.<...>`. The literal -// substrings are also matched in case a config (e.g. wireguard) embeds -// them directly. -function isPostQuantumLink(link: string): boolean { - if (/[?&]pqv=/.test(link)) return true; - if (link.includes('mlkem768') || link.includes('mldsa65')) return true; - if (link.includes('ML-KEM-768')) return true; - return false; -} - // Decode a base64 string as UTF-8. atob() returns a binary string where // each char holds one raw byte (Latin-1 interpretation), which mangles // any multi-byte UTF-8 sequence in the payload — most commonly the