From e5d3ec79089c9fb79a2d2b5b89dd95ada78adebd Mon Sep 17 00:00:00 2001 From: Elias <73573019+303562915@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:42:55 +0800 Subject: [PATCH] feat(mysql): support Navicat HTTP tunnels on CGI/FastCGI hosts (#12494) Add the Navicat MySQL HTTP transport for CGI/FastCGI hosts, including protocol selection, response translation, and prepared-query fallback. Keep the proprietary PHP script outside the repository and document authorized setup. Reject transaction control and autocommit changes before sending HTTP requests, preserve plain HTTP URLs in both tunnel editors, and redact credentials from tunnel startup logs. Detect unsupported DBX PHP worker runtimes before opening a tunnel. Validation: driver-support tests and Clippy, connection UI tests, frontend type checking and formatting, protocol-level transaction rejection and URL regression checks, and PR CI. Co-authored-by: zipg <4047349+zipg@users.noreply.github.com> --- .github/pr-assets/navicat-tunnel-ui.png | Bin 0 -> 152413 bytes Cargo.lock | 2 + agents/drivers/duckdb/Cargo.lock | 10 + .../connection/ConnectionDialog.vue | 180 +++-- .../connection/TunnelProfileManager.vue | 106 ++- .../__tests__/useNavigationTargets.spec.ts | 6 + .../useSidebarDataOpenRuntime.spec.ts | 4 + .../composables/useSidebarDataOpenRuntime.ts | 20 +- apps/desktop/src/i18n/locales/en.ts | 7 + apps/desktop/src/i18n/locales/es.ts | 6 + apps/desktop/src/i18n/locales/it.ts | 6 + apps/desktop/src/i18n/locales/ja.ts | 6 + apps/desktop/src/i18n/locales/ko.ts | 6 + apps/desktop/src/i18n/locales/pt-BR.ts | 6 + apps/desktop/src/i18n/locales/ru.ts | 6 + apps/desktop/src/i18n/locales/th.ts | 6 + apps/desktop/src/i18n/locales/zh-CN.ts | 6 + apps/desktop/src/i18n/locales/zh-TW.ts | 6 + .../src/lib/connection/tunnelProfiles.ts | 1 + apps/desktop/src/types/database.ts | 1 + .../src/connection/connection_secrets.rs | 1 + crates/dbx-core/src/connection/mod.rs | 95 ++- crates/dbx-core/src/persistence/cloud_sync.rs | 1 + crates/dbx-core/src/persistence/storage.rs | 1 + crates/dbx-driver-mysql/src/mysql.rs | 8 + crates/dbx-driver-support/Cargo.toml | 4 +- crates/dbx-driver-support/src/http_tunnel.rs | 78 +- crates/dbx-driver-support/src/lib.rs | 1 + .../src/navicat_mysql_tunnel.rs | 760 ++++++++++++++++++ .../src/db/transport_layer_tunnel.rs | 1 + crates/dbx-types/src/models/connection.rs | 10 + deploy/NAVICAT_HTTP_TUNNEL.md | 28 + deploy/dbx_tunnel.php | 25 +- deploy/tests/http_tunnel_worker_test.php | 3 + docs/content/docs/ssh-tunnel.cn.mdx | 4 +- docs/content/docs/ssh-tunnel.mdx | 4 +- 36 files changed, 1325 insertions(+), 90 deletions(-) create mode 100644 .github/pr-assets/navicat-tunnel-ui.png create mode 100644 crates/dbx-driver-support/src/navicat_mysql_tunnel.rs create mode 100644 deploy/NAVICAT_HTTP_TUNNEL.md diff --git a/.github/pr-assets/navicat-tunnel-ui.png b/.github/pr-assets/navicat-tunnel-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..1a97ac98c59977a7a0c75aec92013606052c182a GIT binary patch literal 152413 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: HttpTunnelConfig enabled: layer.enabled !== false, url: layer.url || "", token: layer.token || "", + protocol: layer.protocol === "navicat_mysql" ? "navicat_mysql" : "dbx_tcp", connect_timeout_secs: Number(layer.connect_timeout_secs) || 10, profile_id: layer.profile_id || undefined, }; @@ -6603,6 +6605,32 @@ function updateSelectedProxyType(value: unknown) { resetTestState(); } +function updateSelectedHttpTunnelProtocol(value: unknown) { + const layer = selectedHttpTunnelLayer.value; + if (!layer) return; + layer.protocol = String(value) === "navicat_mysql" ? "navicat_mysql" : "dbx_tcp"; + if (layer.protocol === "navicat_mysql") layer.token = ""; + if (layer.protocol === "navicat_mysql" && layer.url.trim()) { + try { + const url = new URL(layer.url.trim()); + if (url.pathname.endsWith("/")) { + url.pathname += "ntunnel_mysql.php"; + layer.url = url.toString(); + } else { + layer.url = url.toString(); + } + } catch { + // Keep invalid input unchanged so the submit validation can report it. + } + } + resetTestState(); +} + +const selectedHttpTunnelProtocol = computed({ + get: () => selectedHttpTunnelLayer.value?.protocol || "dbx_tcp", + set: (value: string) => updateSelectedHttpTunnelProtocol(value), +}); + function updateSelectedSshAuthMethod(value: unknown) { const layer = selectedSshLayer.value; if (!layer) return; @@ -7871,7 +7899,7 @@ function openExternalUrl(url: string) {
- + @@ -10645,10 +10673,10 @@ function openExternalUrl(url: string) { -
-
+
+
-
+
@@ -11325,6 +11385,34 @@ function openExternalUrl(url: string) { grid-auto-rows: max-content; } +/* The transport editor contains nested four-column rows. Keep the outer + * scrolling container strictly vertical so legacy WebViews cannot auto-place + * the rows beside each other when a tunnel protocol changes its fields. */ +.connection-transport-form-body { + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + align-content: stretch !important; +} + +.connection-transport-form-body > .connection-label-wide-grid, +.connection-transport-form-body > .grid { + width: 100% !important; + min-width: 0 !important; + flex: 0 0 auto !important; +} + +.connection-transport-form-body > .grid.grid-cols-4, +.connection-transport-form-body > .connection-label-wide-grid.grid-cols-4 { + grid-template-columns: minmax(7rem, 0.8fr) repeat(3, minmax(0, 1fr)) !important; +} + +.connection-transport-form-body > .grid.grid-cols-4 > .col-span-3, +.connection-transport-form-body > .connection-label-wide-grid.grid-cols-4 > .col-span-3 { + grid-column: 2 / -1 !important; + min-width: 0 !important; +} + @media (max-height: 720px) { .connection-dialog-content--config { /* A definite flex height lets tab bodies shrink and scroll above the fixed footer. */ diff --git a/apps/desktop/src/components/connection/TunnelProfileManager.vue b/apps/desktop/src/components/connection/TunnelProfileManager.vue index fc7b391c1d..e73339854a 100644 --- a/apps/desktop/src/components/connection/TunnelProfileManager.vue +++ b/apps/desktop/src/components/connection/TunnelProfileManager.vue @@ -134,6 +134,26 @@ function updateProxyType(value: unknown) { profile.proxy_type = value === "http" ? "http" : "socks5"; } +function updateHttpTunnelProtocol(value: unknown) { + const profile = selectedHttp.value; + if (!profile) return; + profile.protocol = String(value) === "navicat_mysql" ? "navicat_mysql" : "dbx_tcp"; + if (profile.protocol === "navicat_mysql") profile.token = ""; + if (profile.protocol === "navicat_mysql" && profile.url.trim()) { + try { + const url = new URL(profile.url.trim()); + if (url.pathname.endsWith("/")) { + url.pathname += "ntunnel_mysql.php"; + profile.url = url.toString(); + } else { + profile.url = url.toString(); + } + } catch { + // Keep invalid input unchanged so save/connect validation can report it. + } + } +} + async function browseSshKeyPath(target?: { key_path?: string } | null) { if (isTauriRuntime()) { const { open } = await import("@tauri-apps/plugin-dialog"); @@ -244,20 +264,22 @@ async function testSelected() {
-
+
- +
+ +
@@ -303,17 +325,19 @@ async function testSelected() { diff --git a/apps/desktop/src/composables/__tests__/useNavigationTargets.spec.ts b/apps/desktop/src/composables/__tests__/useNavigationTargets.spec.ts index 9c66561b21..98dd72f793 100644 --- a/apps/desktop/src/composables/__tests__/useNavigationTargets.spec.ts +++ b/apps/desktop/src/composables/__tests__/useNavigationTargets.spec.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { createPinia, setActivePinia } from "pinia"; import { useNavigationTargets } from "@/composables/useNavigationTargets"; import { clearTableMetadataCache } from "@/lib/metadata/tableMetadataCache"; import type { QueryTab } from "@/types/database"; @@ -87,6 +88,10 @@ vi.mock("@/stores/settingsStore", () => ({ useSettingsStore: () => ({ editorSettings: { dataTabReuseMode: "same-table", tableOpenSortMode: mocks.tableOpenSortMode, tableDatabaseSortDirection: "desc", tableLocalSortDirection: "asc" } }), })); +vi.mock("@/stores/tunnelProfileStore", () => ({ + useTunnelProfileStore: () => ({ profileById: () => undefined }), +})); + vi.mock("@/lib/table/tableSelectSql", async (importOriginal) => ({ ...(await importOriginal()), buildTableSelectSql: mocks.buildTableSelectSql, @@ -104,6 +109,7 @@ function column(name: string) { describe("useNavigationTargets openTableTarget", () => { beforeEach(() => { + setActivePinia(createPinia()); clearTableMetadataCache(); vi.clearAllMocks(); mocks.tabs.length = 0; diff --git a/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts b/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts index 523def77f4..145aadfa10 100644 --- a/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts +++ b/apps/desktop/src/composables/__tests__/useSidebarDataOpenRuntime.spec.ts @@ -109,6 +109,10 @@ vi.mock("@/stores/settingsStore", () => ({ useSettingsStore: () => ({ editorSettings: { tableOpenSortMode: mocks.tableOpenSortMode, tableDatabaseSortDirection: "desc", tableLocalSortDirection: "asc", dataTabReuseMode: mocks.dataTabReuseMode, openDataTabsNextToActive: mocks.openDataTabsNextToActive, pageSize: 100 } }), })); +vi.mock("@/stores/tunnelProfileStore", () => ({ + useTunnelProfileStore: () => ({ profileById: () => undefined }), +})); + vi.mock("@/lib/database/jdbcDialect", () => ({ effectiveDatabaseTypeForConnection: () => mocks.databaseType, connectionObjectTreeNodeSchema: (_config: unknown, _database: string, schema?: string) => schema, diff --git a/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts b/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts index d2b9278e6c..87470e02bf 100644 --- a/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts +++ b/apps/desktop/src/composables/useSidebarDataOpenRuntime.ts @@ -2,6 +2,7 @@ import { nextTick } from "vue"; import { useConnectionStore } from "@/stores/connectionStore"; import { useQueryStore } from "@/stores/queryStore"; import { useSettingsStore } from "@/stores/settingsStore"; +import { useTunnelProfileStore } from "@/stores/tunnelProfileStore"; import type { TreeNode } from "@/types/database"; import { uuid } from "@/lib/common/utils"; import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog"; @@ -30,6 +31,7 @@ export function useSidebarDataOpenRuntime() { const connectionStore = useConnectionStore(); const queryStore = useQueryStore(); const settingsStore = useSettingsStore(); + const tunnelProfileStore = useTunnelProfileStore(); async function openData(node: TreeNode, request?: SidebarDataOpenRequest, openMode: DataTabOpenMode = "default", options: NavigationOpenOptions & { reuseMode?: DataTabReuseMode; revealColumn?: string } = {}) { if (options.isCurrent?.() === false) return; @@ -92,6 +94,18 @@ export function useSidebarDataOpenRuntime() { const querySchema = config ? connectionObjectTreeQuerySchema(config, node.database, tableSchema) : (tableSchema ?? ""); const effectiveDbType = effectiveDatabaseTypeForConnection(config); const metadataDatabaseType = effectiveDbType || config?.db_type || ""; + // Navicat's PHP tunnel turns every pooled MySQL session into a separate + // HTTP request. Loading columns and indexes before the first data page + // therefore creates several competing handshakes and makes the table + // appear stuck in "loading" even when SELECT itself is already fast. + // Keep the existing eager metadata behavior for every other transport. + const isNavicatMysqlTunnel = + config?.transport_layers?.some((layer) => { + if (layer.type !== "http_tunnel") return false; + if (layer.protocol === "navicat_mysql") return true; + const profile = tunnelProfileStore.profileById(layer.profile_id); + return profile?.type === "http_tunnel" && profile.protocol === "navicat_mysql"; + }) ?? false; const dataTabTarget = { connectionId: node.connectionId, database: node.database, @@ -371,7 +385,7 @@ export function useSidebarDataOpenRuntime() { logPhase("metadata-started", { tabId }); } - const metadataRefresh = shouldRefreshTableMeta && !deferTableMetaRefresh ? refreshTableMetaInBackground(tabId) : undefined; + const metadataRefresh = shouldRefreshTableMeta && !deferTableMetaRefresh && !isNavicatMysqlTunnel ? refreshTableMetaInBackground(tabId) : undefined; if (!cachedTableMeta && (requiresEagerTableMetadataForDataOpen(effectiveDbType) || defaultSortMode !== "none")) { // No query is in flight yet, so deferred drivers can safely load metadata serially. if (deferTableMetaRefresh) await refreshTableMetaInBackground(tabId); @@ -440,9 +454,9 @@ export function useSidebarDataOpenRuntime() { applyTableDefaultSortResult(sortedTab, defaultSort, queryStore.sortTabResultLocally); } logPhase("execute-tab-sql", { tabId }); - if (shouldRefreshTableMeta && deferTableMetaRefresh && canApplyTableMetadata(tabId)) { + if (shouldRefreshTableMeta && (deferTableMetaRefresh || isNavicatMysqlTunnel) && canApplyTableMetadata(tabId)) { void refreshTableMetaInBackground(tabId); - logPhase("metadata-started", { tabId }); + logPhase("metadata-started", { tabId, deferredForTransport: isNavicatMysqlTunnel ? "navicat_mysql" : "driver" }); } } catch (e: any) { if (!isActive()) { diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 1c9ff8c623..b46414ce1d 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2023,7 +2023,14 @@ export default { httpTunnel: "HTTP Tunnel", httpTunnelAdd: "Add HTTP tunnel", httpTunnelUrl: "Tunnel Script URL", + httpTunnelProtocol: "Tunnel Protocol", + httpTunnelProtocolDbx: "DBX TCP script", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: + "MySQL only. Get ntunnel_mysql.php from your Navicat installation, upload it to a PHP server, and enter its full URL. HTTP and HTTPS are supported; HTTP sends credentials and SQL in cleartext, so use it only on a trusted private network. If the server redirects, enter the final HTTPS URL. MySQL SSL and other tunnel layers are not supported.", + httpTunnelNavicatScriptHelp: "Where to get the official PHP script", httpTunnelToken: "Tunnel Token", + httpTunnelNavicatNoToken: "Not used by the Navicat protocol", httpTunnelTokenPlaceholder: "Token configured in dbx_tunnel.php", httpTunnelConnectTimeout: "Tunnel Timeout (seconds)", httpTunnelDefaultName: "HTTP Tunnel {index}", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 0e622c00db..5068e10368 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -1917,6 +1917,12 @@ export default withEnglishFallback({ pluginSshKeyPickerEmpty: "No se encontraron claves", pluginSshKeyEncrypted: "Cifrado", objectCacheRefreshFailed: "Error al actualizar la caché de objetos: {message}", + httpTunnelProtocol: "Protocolo de túnel", + httpTunnelProtocolDbx: "Script TCP de DBX", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "Solo compatible con MySQL; admite direcciones HTTP/HTTPS, se recomienda usar HTTPS. No se puede habilitar MySQL SSL u otro túnel al mismo tiempo.", + httpTunnelNavicatNoToken: "El protocolo Navicat no utiliza Token", + httpTunnelNavicatScriptHelp: "Ver cómo obtener el script oficial de PHP", redisCaCert: "Certificado de CA (opcional)", redisCaCertPlaceholder: "Ruta del certificado CA en formato PEM", redisCaCertBrowse: "Seleccionar certificado CA de Redis", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 902b1bc039..f378cd15cc 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -1826,6 +1826,12 @@ export default withEnglishFallback({ pluginSshKeyPickerEmpty: "Nessuna chiave trovata", pluginSshKeyEncrypted: "Crittografata", objectCacheRefreshFailed: "Aggiornamento della cache degli oggetti non riuscito: {message}", + httpTunnelProtocol: "Protocollo tunnel", + httpTunnelProtocolDbx: "Script DBX TCP", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "Supporta solo MySQL; supporta indirizzi HTTP/HTTPS, si consiglia HTTPS. Non è possibile abilitare contemporaneamente MySQL SSL o un altro tunnel.", + httpTunnelNavicatNoToken: "Il protocollo Navicat non utilizza Token", + httpTunnelNavicatScriptHelp: "Scopri come ottenere lo script PHP ufficiale", redisCaCert: "Certificato CA (opzionale)", redisCaCertPlaceholder: "Percorso del certificato CA in formato PEM", redisCaCertBrowse: "Seleziona certificato CA Redis", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 185876550d..d3d8f73836 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -1847,6 +1847,12 @@ export default withEnglishFallback({ pluginSshKeyPickerEmpty: "キーが見つかりません", pluginSshKeyEncrypted: "暗号化済み", objectCacheRefreshFailed: "オブジェクトキャッシュの更新に失敗しました:{message}", + httpTunnelProtocol: "トンネルプロトコル", + httpTunnelProtocolDbx: "DBX TCP スクリプト", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "MySQL のみ対応;HTTP/HTTPS アドレスをサポートし、HTTPS の使用を推奨します。MySQL SSL または他のトンネルと同時に有効にできません。", + httpTunnelNavicatNoToken: "Navicat プロトコルでは Token を使用しません", + httpTunnelNavicatScriptHelp: "公式PHPスクリプトの取得方法を確認", redisCaCert: "CA 証明書(任意)", redisCaCertPlaceholder: "PEM 形式の CA 証明書パス", redisCaCertBrowse: "Redis CA 証明書を選択", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 6363768c4d..12232b8679 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -1749,6 +1749,12 @@ export default withEnglishFallback({ mqttDirectionFilterAll: "전체", mqttDirectionFilterReceived: "수신됨", mqttDirectionFilterSent: "발신됨", + httpTunnelProtocol: "터널 프로토콜", + httpTunnelProtocolDbx: "DBX TCP 스크립트", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "MySQL만 지원합니다. HTTP/HTTPS 주소를 지원하며 HTTPS 사용을 권장합니다. MySQL SSL 또는 다른 터널을 동시에 활성화할 수 없습니다.", + httpTunnelNavicatNoToken: "Navicat 프로토콜은 Token을 사용하지 않습니다", + httpTunnelNavicatScriptHelp: "공식 PHP 스크립트 가져오기 방법 보기", redisCaCert: "CA 인증서(선택 사항)", redisCaCertPlaceholder: "PEM 형식 CA 인증서 경로", redisCaCertBrowse: "Redis CA 인증서 선택", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 0b73412571..f70bd25eac 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -1827,6 +1827,12 @@ export default withEnglishFallback({ pluginSshKeyPickerEmpty: "Nenhuma chave encontrada", pluginSshKeyEncrypted: "Criptografada", objectCacheRefreshFailed: "Falha ao atualizar o cache de objetos: {message}", + httpTunnelProtocol: "Protocolo de túnel", + httpTunnelProtocolDbx: "Script TCP DBX", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "Somente MySQL é suportado; endereços HTTP/HTTPS são suportados, recomenda-se usar HTTPS. Não é possível habilitar MySQL SSL ou outro túnel ao mesmo tempo.", + httpTunnelNavicatNoToken: "O protocolo Navicat não usa Token", + httpTunnelNavicatScriptHelp: "Ver como obter o script PHP oficial", redisCaCert: "Certificado CA (opcional)", redisCaCertPlaceholder: "Caminho do certificado CA no formato PEM", redisCaCertBrowse: "Selecionar certificado CA do Redis", diff --git a/apps/desktop/src/i18n/locales/ru.ts b/apps/desktop/src/i18n/locales/ru.ts index 1d4c5da048..157c85050b 100644 --- a/apps/desktop/src/i18n/locales/ru.ts +++ b/apps/desktop/src/i18n/locales/ru.ts @@ -1827,6 +1827,12 @@ export default withEnglishFallback({ cancelConnecting: "Отменить подключение", connectCancelled: "Подключение отменено", sshTotpCancelled: "Ввод одноразового кода отменён", + httpTunnelProtocol: "Протокол туннеля", + httpTunnelProtocolDbx: "Скрипт DBX TCP", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "Поддерживается только MySQL; поддерживаются адреса HTTP/HTTPS, рекомендуется использовать HTTPS. Нельзя одновременно включать MySQL SSL или другой туннель.", + httpTunnelNavicatNoToken: "Протокол Navicat не использует токен", + httpTunnelNavicatScriptHelp: "Посмотреть, как получить официальный PHP-скрипт", redisCaCert: "Сертификат CA (необязательно)", redisCaCertPlaceholder: "Путь к сертификату CA в формате PEM", redisCaCertBrowse: "Выбрать CA-сертификат Redis", diff --git a/apps/desktop/src/i18n/locales/th.ts b/apps/desktop/src/i18n/locales/th.ts index 5b8202442f..dd01354b18 100644 --- a/apps/desktop/src/i18n/locales/th.ts +++ b/apps/desktop/src/i18n/locales/th.ts @@ -2024,6 +2024,12 @@ export default withEnglishFallback({ cancelConnecting: "ยกเลิกการเชื่อมต่อ", connectCancelled: "ยกเลิกการเชื่อมต่อแล้ว", sshTotpCancelled: "ยกเลิกการป้อน Code แบบใช้ครั้งเดียวแล้ว", + httpTunnelProtocol: "โปรโตคอลทันเนล", + httpTunnelProtocolDbx: "สคริปต์ DBX TCP", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "รองรับเฉพาะ MySQL; รองรับที่อยู่ HTTP/HTTPS แนะนำให้ใช้ HTTPS ไม่สามารถเปิดใช้งาน MySQL SSL หรือทันเนลอื่นพร้อมกันได้", + httpTunnelNavicatNoToken: "โปรโตคอล Navicat ไม่ใช้ Token", + httpTunnelNavicatScriptHelp: "ดูวิธีการรับสคริปต์ PHP อย่างเป็นทางการ", redisCaCert: "ใบรับรอง CA (ไม่บังคับ)", redisCaCertPlaceholder: "พาธของใบรับรอง CA ในรูปแบบ PEM", redisCaCertBrowse: "เลือกใบรับรอง CA ของ Redis", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 41bd1ee008..98c77428ec 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -1960,7 +1960,13 @@ export default withEnglishFallback({ httpTunnel: "HTTP 隧道", httpTunnelAdd: "添加 HTTP 隧道", httpTunnelUrl: "隧道脚本 URL", + httpTunnelProtocol: "隧道协议", + httpTunnelProtocolDbx: "DBX TCP 脚本", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "仅支持 MySQL。从 Navicat 安装目录获取 ntunnel_mysql.php,上传到 PHP 服务器后填写完整 URL。支持 HTTP/HTTPS;HTTP 会明文传输凭据和 SQL,仅用于可信内网。服务器若会重定向,请直接填写最终 HTTPS 地址。不支持同时启用 MySQL SSL 或其他隧道。", + httpTunnelNavicatScriptHelp: "查看官方 PHP 脚本获取方式", httpTunnelToken: "隧道 Token", + httpTunnelNavicatNoToken: "Navicat 协议不使用 Token", httpTunnelTokenPlaceholder: "dbx_tunnel.php 中配置的 token", httpTunnelConnectTimeout: "隧道超时(秒)", httpTunnelDefaultName: "HTTP 隧道 {index}", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index 6a035d7920..50217daba0 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -1827,6 +1827,12 @@ export default withEnglishFallback({ mqttClientKeyPath: "用戶端私鑰路徑", mqttNoLocal: "禁止本機轉送", readOnlyExplanation: "唯讀模式:直接阻止目前連線的寫入操作,需要暫時解鎖後才能寫入。", + httpTunnelProtocol: "隧道協定", + httpTunnelProtocolDbx: "DBX TCP 指令碼", + httpTunnelProtocolNavicat: "Navicat MySQL HTTP", + httpTunnelNavicatHint: "僅支援 MySQL;支援 HTTP/HTTPS 位址,建議使用 HTTPS。不能同時啟用 MySQL SSL 或其他隧道。", + httpTunnelNavicatNoToken: "Navicat 通訊協定不使用 Token", + httpTunnelNavicatScriptHelp: "查看官方 PHP 腳本取得方式", redisCaCert: "CA 憑證(選填)", redisCaCertPlaceholder: "PEM 格式 CA 憑證路徑", redisCaCertBrowse: "選擇 Redis CA 憑證", diff --git a/apps/desktop/src/lib/connection/tunnelProfiles.ts b/apps/desktop/src/lib/connection/tunnelProfiles.ts index fd5f00f8e7..2912d0d29b 100644 --- a/apps/desktop/src/lib/connection/tunnelProfiles.ts +++ b/apps/desktop/src/lib/connection/tunnelProfiles.ts @@ -48,6 +48,7 @@ export function createTunnelProfile(type: TunnelProfileType): TunnelProfile { enabled: true, url: "", token: "", + protocol: "dbx_tcp", connect_timeout_secs: 10, }; } diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 26f572008c..49950c431e 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -380,6 +380,7 @@ export interface HttpTunnelConfig { enabled?: boolean; url: string; token?: string; + protocol?: "dbx_tcp" | "navicat_mysql"; connect_timeout_secs?: number; /** See {@link SshTunnelConfig.profile_id}. */ profile_id?: string; diff --git a/crates/dbx-core/src/connection/connection_secrets.rs b/crates/dbx-core/src/connection/connection_secrets.rs index 39b1120a1d..5a4b13dc00 100644 --- a/crates/dbx-core/src/connection/connection_secrets.rs +++ b/crates/dbx-core/src/connection/connection_secrets.rs @@ -1199,6 +1199,7 @@ mod tests { enabled: true, url: "https://dbx.example.com/dbx_tunnel.php".to_string(), token: token.to_string(), + protocol: crate::models::connection::HttpTunnelProtocol::DbxTcp, connect_timeout_secs: 10, }) } diff --git a/crates/dbx-core/src/connection/mod.rs b/crates/dbx-core/src/connection/mod.rs index 54789e0653..390fb4bd59 100644 --- a/crates/dbx-core/src/connection/mod.rs +++ b/crates/dbx-core/src/connection/mod.rs @@ -3732,9 +3732,70 @@ impl AppState { config: &ConnectionConfig, ) -> Result { let transport_layers = self.resolved_transport_layers(config).await?; + let transport_summary = transport_layers + .iter() + .map(|layer| match layer { + TransportLayerConfig::Ssh(_) => "ssh".to_string(), + TransportLayerConfig::Proxy(_) => "proxy".to_string(), + TransportLayerConfig::HttpTunnel(http) => { + format!("http_tunnel:{:?}:{}", http.protocol, redact_transport_url(&http.url)) + } + }) + .collect::>() + .join(","); + log::info!( + "[transport] connection_id={} db_type={:?} layer_count={} layers=[{}] target={}:{}", + connection_id, + config.db_type, + transport_layers.len(), + transport_summary, + config.host, + config.port + ); + let navicat_layer = transport_layers.iter().enumerate().find_map(|(index, layer)| match layer { + TransportLayerConfig::HttpTunnel(http) + if http.protocol == crate::models::connection::HttpTunnelProtocol::NavicatMysql => + { + Some(index) + } + _ => None, + }); + if let Some(index) = navicat_layer { + if config.db_type != DatabaseType::Mysql { + return Err("Navicat HTTP tunnel protocol is only supported for MySQL connections.".to_string()); + } + if index != 0 || transport_layers.len() != 1 { + return Err("Navicat MySQL HTTP tunnel must be the only transport layer.".to_string()); + } + if config.ssl + || !config.ca_cert_path.is_empty() + || !config.client_cert_path.is_empty() + || !config.client_key_path.is_empty() + { + return Err("Navicat MySQL HTTP tunnel does not support MySQL TLS settings. Disable MySQL SSL or remove the TLS certificate paths.".to_string()); + } + } if transport_layers.is_empty() || db::sqlite_worker::sqlite_remote_worker_requested(config) { return Ok(ConnectionEndpoint::direct(config.host.clone(), config.port)); } + if let Some(TransportLayerConfig::HttpTunnel(http)) = transport_layers.first() { + if http.protocol == crate::models::connection::HttpTunnelProtocol::NavicatMysql { + let local_port = self + .http_tunnels + .start_navicat_mysql_tunnel( + connection_id, + &http.url, + http.connect_timeout_secs, + &config.host, + config.port, + &config.username, + &config.password, + config.database.as_deref(), + ) + .await?; + return Ok(ConnectionEndpoint::direct("127.0.0.1".to_string(), local_port)); + } + } if config.db_type == DatabaseType::ClickHouse || config.db_type == DatabaseType::CloudflareD1 || config.db_type == DatabaseType::Turso @@ -6406,6 +6467,18 @@ impl AppState { } } +fn redact_transport_url(raw: &str) -> String { + reqwest::Url::parse(raw) + .map(|mut url| { + let _ = url.set_username(""); + let _ = url.set_password(None); + url.set_query(None); + url.set_fragment(None); + url.to_string() + }) + .unwrap_or_else(|_| "".to_string()) +} + fn gaussdb_identifier_quote_from_query_result(result: &db::QueryResult) -> Option { let compatibility_mode = result.rows.first()?.first()?.as_str()?; db::postgres::gaussdb_identifier_quote_for_compatibility_mode(compatibility_mode).map(str::to_string) @@ -7603,12 +7676,12 @@ mod tests { gaussdb_identifier_quote_from_query_result, gaussdb_m_jdbc_config_for_endpoint, gaussdb_uses_m_jdbc_driver, kafka_single_loopback_bootstrap_endpoint, keepalive_failure_proves_pool_dead, metadata_connection_config, metadata_pool_database, mysql_metadata_fallback_url, mysql_pool_setup_queries, oceanbase_mysql_setup_queries, - prestosql_jdbc_config_for_endpoint, redacted_connection_url_for_endpoint, redis_sentinel_transport_id, - redis_sentinel_transport_prefix, report_connection_liveness_lost, sqlserver_legacy_agent_config, - sqlserver_legacy_driver_error, sqlserver_uses_legacy_driver, task_client_session_id, - transport_layers_through_last_ssh, upsert_connection_url_param, uses_bare_mysql_pool, uses_tcp_probe, - validate_connection_url_params, validate_h2_database_path, AppState, MysqlMode, PoolKind, TxnConnection, - GAUSSDB_M_JDBC_DRIVER_CLASS, GAUSSDB_M_JDBC_DRIVER_PROFILE, PRESTOSQL_JDBC_DRIVER_CLASS, + prestosql_jdbc_config_for_endpoint, redact_transport_url, redacted_connection_url_for_endpoint, + redis_sentinel_transport_id, redis_sentinel_transport_prefix, report_connection_liveness_lost, + sqlserver_legacy_agent_config, sqlserver_legacy_driver_error, sqlserver_uses_legacy_driver, + task_client_session_id, transport_layers_through_last_ssh, upsert_connection_url_param, uses_bare_mysql_pool, + uses_tcp_probe, validate_connection_url_params, validate_h2_database_path, AppState, MysqlMode, PoolKind, + TxnConnection, GAUSSDB_M_JDBC_DRIVER_CLASS, GAUSSDB_M_JDBC_DRIVER_PROFILE, PRESTOSQL_JDBC_DRIVER_CLASS, }; use crate::agent_connection::{ agent_connect_params, mongo_legacy_error_with_auth_hint, mongo_uses_legacy_driver, @@ -7628,6 +7701,15 @@ mod tests { use std::sync::Arc; use std::time::{Duration, Instant}; + #[test] + fn transport_url_redaction_removes_credentials_query_and_fragment() { + assert_eq!( + redact_transport_url("https://alice:secret@example.test/tunnel.php?token=secret#fragment"), + "https://example.test/tunnel.php" + ); + assert_eq!(redact_transport_url("https://alice:secret@%/"), ""); + } + pub(super) fn mysql_config(database: Option<&str>) -> ConnectionConfig { ConnectionConfig { oracle_oci_nls_lang: None, @@ -11890,6 +11972,7 @@ for line in sys.stdin: enabled: true, url: String::new(), token: String::new(), + protocol: crate::models::connection::HttpTunnelProtocol::DbxTcp, connect_timeout_secs: 10, profile_id: profile_id.to_string(), } diff --git a/crates/dbx-core/src/persistence/cloud_sync.rs b/crates/dbx-core/src/persistence/cloud_sync.rs index 6140901547..4b1bbf6edf 100644 --- a/crates/dbx-core/src/persistence/cloud_sync.rs +++ b/crates/dbx-core/src/persistence/cloud_sync.rs @@ -4153,6 +4153,7 @@ mod tests { enabled: true, url: "https://dbx.example.com/dbx_tunnel.php".to_string(), token: "tunnel-token".to_string(), + protocol: crate::models::connection::HttpTunnelProtocol::DbxTcp, connect_timeout_secs: 10, }), ], diff --git a/crates/dbx-core/src/persistence/storage.rs b/crates/dbx-core/src/persistence/storage.rs index ac7b8b7961..a745033272 100644 --- a/crates/dbx-core/src/persistence/storage.rs +++ b/crates/dbx-core/src/persistence/storage.rs @@ -11761,6 +11761,7 @@ mod tests { enabled: true, url: "https://bastion.example.com".to_string(), token: "tunnel-token".to_string(), + protocol: crate::models::connection::HttpTunnelProtocol::DbxTcp, connect_timeout_secs: 5, profile_id: String::new(), }); diff --git a/crates/dbx-driver-mysql/src/mysql.rs b/crates/dbx-driver-mysql/src/mysql.rs index 3fe667ee96..594fa20c43 100644 --- a/crates/dbx-driver-mysql/src/mysql.rs +++ b/crates/dbx-driver-mysql/src/mysql.rs @@ -2718,6 +2718,7 @@ fn mysql_error_should_retry_with_text_protocol(error: &str) -> bool { || (lower.contains("1615") && lower.contains("re-prepared")) || lower.contains("com_stmt_prepare") || lower.contains("can't parse") + || lower.contains("1295") || lower.contains("buf doesn't have enough data") || lower.contains("prepared statement protocol") || lower.contains("this command is not supported in the prepared statement protocol yet") @@ -9556,6 +9557,13 @@ mod tests { assert!(mysql_error_should_retry_with_text_protocol(error)); } + #[test] + fn mysql_navicat_unsupported_prepare_error_can_retry_with_text_protocol() { + let error = "Server error: ERROR 1295 (HY000): Prepared statements are not supported by Navicat HTTP tunnels"; + + assert!(mysql_error_should_retry_with_text_protocol(error)); + } + #[test] fn mysql_reprepared_statement_error_can_retry_with_text_protocol() { let error = "Server error: ERROR HY000 (1615): Prepared statement needs to be re-prepared"; diff --git a/crates/dbx-driver-support/Cargo.toml b/crates/dbx-driver-support/Cargo.toml index e2c3bb2507..684111c18d 100644 --- a/crates/dbx-driver-support/Cargo.toml +++ b/crates/dbx-driver-support/Cargo.toml @@ -13,9 +13,11 @@ test-support = ["dbx-platform/test-support"] dbx-platform = { path = "../dbx-platform" } dbx-types = { path = "../dbx-types", default-features = false } bytes = "1" +base64 = "0.22" glob = "0.3" +log = "0.4" portpicker = "0.1.1" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "socks"] } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "charset", "socks"] } russh = "0.60" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision", "preserve_order"] } diff --git a/crates/dbx-driver-support/src/http_tunnel.rs b/crates/dbx-driver-support/src/http_tunnel.rs index 4e6132e8f0..f34193d02f 100644 --- a/crates/dbx-driver-support/src/http_tunnel.rs +++ b/crates/dbx-driver-support/src/http_tunnel.rs @@ -38,6 +38,15 @@ impl HttpTunnelManager { return Ok(local_port); } let local_port = portpicker::pick_unused_port().ok_or("No available port")?; + log::info!( + "[http-tunnel] starting dbx_tcp connection_id={} local_port={} url={} target={}:{} timeout_secs={}", + connection_id, + local_port, + redacted_tunnel_url(tunnel_url), + remote_host, + remote_port, + connect_timeout_secs + ); let listener = TcpListener::bind(("127.0.0.1", local_port)) .await .map_err(|e| format!("Failed to bind HTTP tunnel local port: {e}"))?; @@ -56,6 +65,52 @@ impl HttpTunnelManager { Ok(local_port) } + pub async fn start_navicat_mysql_tunnel( + &self, + connection_id: &str, + tunnel_url: &str, + connect_timeout_secs: u64, + remote_host: &str, + remote_port: u16, + username: &str, + password: &str, + database: Option<&str>, + ) -> Result { + if let Some(local_port) = self.local_port(connection_id).await { + return Ok(local_port); + } + let local_port = portpicker::pick_unused_port().ok_or("No available port")?; + log::info!( + "[http-tunnel] starting navicat_mysql connection_id={} local_port={} url={} target={}:{} timeout_secs={}", + connection_id, + local_port, + redacted_tunnel_url(tunnel_url), + remote_host, + remote_port, + connect_timeout_secs + ); + let listener = TcpListener::bind(("127.0.0.1", local_port)) + .await + .map_err(|error| format!("Failed to bind Navicat MySQL tunnel local port: {error}"))?; + let endpoint = crate::navicat_mysql_tunnel::NavicatMysqlEndpoint::new( + tunnel_url, + remote_host, + remote_port, + username, + password, + database, + connect_timeout_secs, + )?; + let handle = tokio::spawn(crate::navicat_mysql_tunnel::serve(listener, endpoint)); + let mut tunnels = self.tunnels.lock().await; + if let Some((_, existing_port)) = tunnels.get(connection_id) { + handle.abort(); + return Ok(*existing_port); + } + tunnels.insert(connection_id.to_string(), (handle, local_port)); + Ok(local_port) + } + pub async fn local_port(&self, connection_id: &str) -> Option { self.tunnels.lock().await.get(connection_id).map(|(_, port)| *port) } @@ -84,6 +139,18 @@ impl HttpTunnelManager { } } +fn redacted_tunnel_url(raw: &str) -> String { + reqwest::Url::parse(raw) + .map(|mut url| { + let _ = url.set_username(""); + let _ = url.set_password(None); + url.set_query(None); + url.set_fragment(None); + url.to_string() + }) + .unwrap_or_else(|_| "".to_string()) +} + struct HttpTunnelEndpoint { url: String, token: String, @@ -300,13 +367,22 @@ fn effective_connect_timeout_secs(value: u64) -> u64 { #[cfg(test)] mod tests { - use super::{script_url, validate_script_url, HttpTunnelManager}; + use super::{redacted_tunnel_url, script_url, validate_script_url, HttpTunnelManager}; use std::sync::Arc; use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{Mutex, Notify}; use tokio::time::{timeout, Duration}; + #[test] + fn tunnel_log_url_removes_credentials_and_does_not_echo_invalid_input() { + assert_eq!( + redacted_tunnel_url("https://alice:secret@example.test/tunnel.php?token=secret#fragment"), + "https://example.test/tunnel.php" + ); + assert_eq!(redacted_tunnel_url("https://alice:secret@%/"), ""); + } + #[test] fn script_url_preserves_existing_query_and_appends_action() { let url = script_url( diff --git a/crates/dbx-driver-support/src/lib.rs b/crates/dbx-driver-support/src/lib.rs index ff6952a975..6dcb81a745 100644 --- a/crates/dbx-driver-support/src/lib.rs +++ b/crates/dbx-driver-support/src/lib.rs @@ -10,6 +10,7 @@ pub mod elasticsearch_sql; pub mod execution; pub mod file_validator; pub mod http_tunnel; +pub mod navicat_mysql_tunnel; pub mod query_messages; pub mod runtime_config; pub mod ssh_config; diff --git a/crates/dbx-driver-support/src/navicat_mysql_tunnel.rs b/crates/dbx-driver-support/src/navicat_mysql_tunnel.rs new file mode 100644 index 0000000000..632b1d0f5d --- /dev/null +++ b/crates/dbx-driver-support/src/navicat_mysql_tunnel.rs @@ -0,0 +1,760 @@ +use base64::Engine; +use reqwest::{Client, Url}; +use sqlparser::dialect::MySqlDialect; +use sqlparser::tokenizer::{Token, Tokenizer, Whitespace}; +use std::sync::Arc; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::task::JoinSet; +use tokio::time::{timeout, Duration}; + +const CLIENT_PROTOCOL_41: u32 = 0x0000_0200; +const CLIENT_SECURE_CONNECTION: u32 = 0x0000_8000; +const CLIENT_PLUGIN_AUTH: u32 = 0x0008_0000; +const SERVER_CAPABILITIES: u32 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH; +const MAX_PACKET_SIZE: usize = 16 * 1024 * 1024; + +#[derive(Clone)] +pub struct NavicatMysqlEndpoint { + url: Url, + host: String, + port: u16, + username: String, + password: String, + database: String, + request_timeout: Duration, + client: Client, +} + +impl NavicatMysqlEndpoint { + pub fn new( + url: &str, + host: &str, + port: u16, + username: &str, + password: &str, + database: Option<&str>, + request_timeout_secs: u64, + ) -> Result { + let url = Url::parse(url.trim()).map_err(|error| format!("Invalid Navicat tunnel URL: {error}"))?; + match url.scheme() { + "https" => {} + "http" => log::warn!( + "[navicat-http] using unencrypted HTTP tunnel; database credentials and SQL are exposed in transit" + ), + other => return Err(format!("Unsupported Navicat tunnel URL scheme: {other}")), + } + if !url.path().to_ascii_lowercase().ends_with(".php") { + return Err( + "Navicat tunnel URL must point to the PHP tunnel script, for example https://example.com/ntunnel_mysql.php" + .to_string(), + ); + } + if host.trim().is_empty() || port == 0 { + return Err("Navicat tunnel requires a MySQL target host and port".to_string()); + } + if username.is_empty() { + return Err("Navicat tunnel requires a MySQL username".to_string()); + } + Ok(Self { + url, + host: host.to_string(), + port, + username: username.to_string(), + password: password.to_string(), + database: database.unwrap_or_default().to_string(), + request_timeout: Duration::from_secs(request_timeout_secs.clamp(1, 300)), + client: Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .map_err(|error| format!("Failed to create Navicat HTTP client: {error}"))?, + }) + } + + async fn query(&self, database: &str, sql: &str) -> Result, String> { + let started = std::time::Instant::now(); + let encoded_query = base64::engine::general_purpose::STANDARD.encode(sql.as_bytes()); + let fields = [ + ("actn", "Q".to_string()), + ("host", self.host.clone()), + ("port", self.port.to_string()), + ("login", self.username.clone()), + ("password", self.password.clone()), + ("db", database.to_string()), + ("encodeBase64", "1".to_string()), + ("q[]", encoded_query), + ]; + log::debug!( + "[navicat-http] POST tunnel request target={}:{} db={} sql_bytes={} timeout_secs={}", + self.host, + self.port, + if database.is_empty() { "" } else { "" }, + sql.len(), + self.request_timeout.as_secs() + ); + let (status, response_url, content_type, body) = timeout(self.request_timeout, async { + let response = self.client.post(self.url.clone()).form(&fields).send().await?; + let status = response.status(); + let mut response_url = response.url().clone(); + let _ = response_url.set_username(""); + let _ = response_url.set_password(None); + response_url.set_query(None); + response_url.set_fragment(None); + let content_type = response + .headers() + .get(reqwest::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(str::to_string); + let body = response.bytes().await?; + Ok::<_, reqwest::Error>((status, response_url, content_type, body)) + }) + .await + .map_err(|_| { + log::warn!( + "[navicat-http] request timed out after {:?} target={}:{}", + started.elapsed(), + self.host, + self.port + ); + "Navicat HTTP tunnel request timed out".to_string() + })? + .map_err(|error| { + log::warn!( + "[navicat-http] request transport error after {:?} target={}:{}: {error}", + started.elapsed(), + self.host, + self.port + ); + format!("Navicat HTTP tunnel request failed: {error}") + })?; + log::debug!( + "[navicat-http] response status={} content_type={:?} final_url={} bytes={} elapsed={:?}", + status, + content_type, + response_url, + body.len(), + started.elapsed() + ); + if !status.is_success() { + let text = String::from_utf8_lossy(&body); + return Err(format!("Navicat HTTP tunnel returned HTTP {status}: {}", text.trim())); + } + if body.len() < 16 + || u32::from_be_bytes(body[0..4].try_into().unwrap()) != 1111 + || u16::from_be_bytes(body[4..6].try_into().unwrap()) != 204 + { + let header_bytes = body.iter().take(16).map(|byte| format!("{byte:02x}")).collect::>().join(" "); + let preview = String::from_utf8_lossy(body.get(..body.len().min(120)).unwrap_or_default()) + .chars() + .map(|ch| if ch.is_control() { ' ' } else { ch }) + .collect::(); + let message = format!( + "Invalid Navicat HTTP tunnel response: HTTP {status}, content-type={}, final_url={}, bytes={}, header=[{}], preview={:?}", + content_type.as_deref().unwrap_or(""), + response_url, + body.len(), + header_bytes, + preview.trim() + ); + log::warn!("[navicat-http] {message}"); + return Err(message); + } + Ok(body.to_vec()) + } +} + +pub async fn serve(listener: TcpListener, endpoint: NavicatMysqlEndpoint) { + let endpoint = Arc::new(endpoint); + let mut clients = JoinSet::new(); + loop { + tokio::select! { + accepted = listener.accept() => match accepted { + Ok((stream, _)) => { + let peer = stream.peer_addr().ok(); + let endpoint = endpoint.clone(); + clients.spawn(async move { + let mut stream = stream; + if let Err(error) = serve_client(&mut stream, endpoint).await { + log::warn!("[navicat-mysql] local client session failed peer={peer:?}: {error}"); + let _ = send_mysql_error(&mut stream, 1, 1105, &error).await; + } + }); + } + Err(_) => break, + }, + _ = clients.join_next(), if !clients.is_empty() => {} + } + } +} + +async fn serve_client(stream: &mut TcpStream, endpoint: Arc) -> Result<(), String> { + let peer = stream.peer_addr().ok(); + let seed_a = uuid::Uuid::new_v4(); + let seed_b = uuid::Uuid::new_v4(); + let mut auth_seed = [0; 20]; + auth_seed[..16].copy_from_slice(seed_a.as_bytes()); + auth_seed[16..].copy_from_slice(&seed_b.as_bytes()[..4]); + let greeting = mysql_handshake_greeting(&auth_seed); + write_packet(stream, 0, &greeting).await?; + log::debug!("[navicat-mysql] sent protocol greeting peer={peer:?} bytes={}", greeting.len()); + + let (sequence, handshake) = read_packet(stream).await.map_err(|error| { + log::warn!("[navicat-mysql] failed waiting for client handshake peer={peer:?}: {error}"); + error + })?; + log::debug!( + "[navicat-mysql] received client handshake peer={peer:?} sequence={sequence} bytes={} capabilities=0x{:08x}", + handshake.len(), + handshake.get(..4).and_then(|bytes| <[u8; 4]>::try_from(bytes).ok()).map(u32::from_le_bytes).unwrap_or(0) + ); + if handshake.len() < 32 { + return Err("Invalid MySQL handshake response".to_string()); + } + let client_capabilities = u32::from_le_bytes(handshake[0..4].try_into().unwrap()); + if client_capabilities & CLIENT_PROTOCOL_41 == 0 { + return send_mysql_error(stream, sequence.wrapping_add(1), 1043, "MySQL protocol 4.1 is required").await; + } + write_packet(stream, sequence.wrapping_add(1), &mysql_ok_packet(0, 0, "")).await?; + log::debug!("[navicat-mysql] authenticated local client peer={peer:?}"); + + let mut database = endpoint.database.clone(); + loop { + let (sequence, packet) = match read_packet(stream).await { + Ok(packet) => packet, + Err(error) if error == "MySQL client disconnected" => return Ok(()), + Err(error) => { + log::warn!("[navicat-mysql] failed reading client command peer={peer:?}: {error}"); + return Err(error); + } + }; + let Some((&command, payload)) = packet.split_first() else { + continue; + }; + log::debug!("[navicat-mysql] received command peer={peer:?} command=0x{command:02x} sequence={sequence} payload_bytes={}", payload.len()); + match command { + 0x01 => return Ok(()), + 0x0e => write_packet(stream, sequence.wrapping_add(1), &mysql_ok_packet(0, 0, "")).await?, + 0x02 => { + database = String::from_utf8_lossy(payload).to_string(); + write_packet(stream, sequence.wrapping_add(1), &mysql_ok_packet(0, 0, "")).await?; + } + 0x03 => { + let sql = String::from_utf8_lossy(payload).to_string(); + if let Err(error) = validate_stateless_query(&sql) { + send_mysql_error(stream, sequence.wrapping_add(1), 1235, &error).await?; + continue; + } + if let Some(next_database) = parse_use_database(&sql) { + database = next_database; + write_packet(stream, sequence.wrapping_add(1), &mysql_ok_packet(0, 0, "")).await?; + continue; + } + match endpoint.query(&database, &sql).await.and_then(|body| parse_navicat_response(&body)) { + Ok(result) => write_query_result(stream, result).await?, + Err(error) => send_mysql_error(stream, sequence.wrapping_add(1), 1105, &error).await?, + } + } + 0x16 => { + send_mysql_error( + stream, + sequence.wrapping_add(1), + 1295, + "Prepared statements are not supported by Navicat HTTP tunnels", + ) + .await? + } + _ => { + send_mysql_error(stream, sequence.wrapping_add(1), 1047, "Unsupported command for Navicat HTTP tunnel") + .await? + } + } + } +} + +fn validate_stateless_query(sql: &str) -> Result<(), String> { + let mut tokens = Vec::new(); + stateless_query_tokens(sql, &mut tokens)?; + for statement in tokens.split(|token| *token == Token::SemiColon) { + let word_is = |token: Option<&Token>, expected: &str| matches!(token, Some(Token::Word(word)) if word.value.eq_ignore_ascii_case(expected)); + let first = statement.first(); + let transaction = + ["BEGIN", "COMMIT", "ROLLBACK", "SAVEPOINT", "XA"].iter().any(|keyword| word_is(first, keyword)) + || (word_is(first, "START") && word_is(statement.get(1), "TRANSACTION")) + || (word_is(first, "RELEASE") && word_is(statement.get(1), "SAVEPOINT")) + || (word_is(first, "SET") + && statement.iter().any(|token| { + word_is(Some(token), "AUTOCOMMIT") + || word_is(Some(token), "@@AUTOCOMMIT") + || word_is(Some(token), "TRANSACTION") + })); + if transaction { + return Err( + "Transactions and autocommit settings are not supported by Navicat HTTP tunnels: each request uses a separate MySQL session" + .to_string(), + ); + } + } + Ok(()) +} + +fn stateless_query_tokens(sql: &str, output: &mut Vec) -> Result<(), String> { + for token in Tokenizer::new(&MySqlDialect {}, sql) + .tokenize() + .map_err(|error| format!("Cannot validate Navicat HTTP tunnel query: {error}"))? + { + match token { + Token::Whitespace(Whitespace::MultiLineComment(comment)) => { + // MySQL executes versioned comments; inspect their SQL as well. + if let Some(body) = comment.strip_prefix('!').or_else(|| comment.strip_prefix("M!")) { + stateless_query_tokens(body.trim_start_matches(|ch: char| ch.is_ascii_digit()), output)?; + } + } + Token::Whitespace(_) | Token::EOF => {} + token => output.push(token), + } + } + Ok(()) +} + +fn mysql_handshake_greeting(auth_seed: &[u8; 20]) -> Vec { + let mut greeting = vec![10]; + greeting.extend_from_slice(b"8.0.0-dbx-navicat\0"); + // MySQL's handshake places a 4-byte connection id before the first + // eight bytes of the authentication seed. Omitting it shifts every + // following field and makes clients wait for a handshake that never + // arrives. + greeting.extend_from_slice(&1u32.to_le_bytes()); + greeting.extend_from_slice(&auth_seed[..8]); + greeting.push(0); // auth-plugin-data-part-1 terminator + greeting.extend_from_slice(&(SERVER_CAPABILITIES as u16).to_le_bytes()); + greeting.push(45); + greeting.extend_from_slice(&2u16.to_le_bytes()); + greeting.extend_from_slice(&((SERVER_CAPABILITIES >> 16) as u16).to_le_bytes()); + greeting.push(21); + greeting.extend_from_slice(&[0; 10]); + greeting.extend_from_slice(&auth_seed[8..]); + greeting.push(0); + greeting.extend_from_slice(b"mysql_native_password\0"); + greeting +} + +#[derive(Debug, PartialEq, Eq)] +struct NavicatResult { + affected_rows: u64, + last_insert_id: u64, + columns: Vec, + rows: Vec>>>, + info: String, +} + +#[derive(Debug, PartialEq, Eq)] +struct NavicatColumn { + name: Vec, + table: Vec, + column_type: u8, + flags: u32, + length: u32, +} + +fn parse_navicat_response(data: &[u8]) -> Result { + if data.len() < 16 || u32::from_be_bytes(data[0..4].try_into().unwrap()) != 1111 { + return Err("Invalid Navicat HTTP tunnel response header".to_string()); + } + if u16::from_be_bytes(data[4..6].try_into().unwrap()) != 204 { + return Err("Unsupported Navicat HTTP tunnel response version".to_string()); + } + let connect_error = u32::from_be_bytes(data[6..10].try_into().unwrap()); + let mut cursor = 16; + if connect_error != 0 { + let message = read_navicat_block(data, &mut cursor)?; + return Err(format!("MySQL connection error {connect_error}: {}", String::from_utf8_lossy(&message))); + } + let mut result = NavicatResult { + affected_rows: 0, + last_insert_id: 0, + columns: Vec::new(), + rows: Vec::new(), + info: String::new(), + }; + if cursor == data.len() { + return Ok(result); + } + // Navicat's result-set header has five 4-byte integers followed by + // twelve reserved bytes: 32 bytes total. + if data.len() - cursor < 32 { + return Err("Truncated Navicat query result header".to_string()); + } + let query_error = u32::from_be_bytes(data[cursor..cursor + 4].try_into().unwrap()); + result.affected_rows = u32::from_be_bytes(data[cursor + 4..cursor + 8].try_into().unwrap()) as u64; + result.last_insert_id = u32::from_be_bytes(data[cursor + 8..cursor + 12].try_into().unwrap()) as u64; + let column_count = u32::from_be_bytes(data[cursor + 12..cursor + 16].try_into().unwrap()) as usize; + let row_count = u32::from_be_bytes(data[cursor + 16..cursor + 20].try_into().unwrap()) as usize; + cursor += 32; + if query_error != 0 { + let message = read_navicat_block(data, &mut cursor)?; + return Err(format!("MySQL query error {query_error}: {}", String::from_utf8_lossy(&message))); + } + for _ in 0..column_count { + let name = read_navicat_block(data, &mut cursor)?; + let table = read_navicat_block(data, &mut cursor)?; + let column_type = read_u32_be(data, &mut cursor)? as u8; + let flags = read_u32_be(data, &mut cursor)?; + let length = read_u32_be(data, &mut cursor)?; + result.columns.push(NavicatColumn { name, table, column_type, flags, length }); + } + for _ in 0..row_count { + let mut row = Vec::with_capacity(column_count); + for _ in 0..column_count { + if data.get(cursor) == Some(&0xff) { + cursor += 1; + row.push(None); + } else { + row.push(Some(read_navicat_block(data, &mut cursor)?)); + } + } + result.rows.push(row); + } + if result.columns.is_empty() { + result.info = String::from_utf8_lossy(&read_navicat_block(data, &mut cursor)?).to_string(); + } + if data.get(cursor) != Some(&0) || cursor + 1 != data.len() { + return Err("Invalid or missing Navicat result terminator".to_string()); + } + Ok(result) +} + +fn read_navicat_block(data: &[u8], cursor: &mut usize) -> Result, String> { + let marker = *data.get(*cursor).ok_or("Truncated Navicat value")?; + *cursor += 1; + let length = match marker { + 0..=253 => marker as usize, + 254 => read_u32_be(data, cursor)? as usize, + 255 => return Err("Unexpected NULL marker in Navicat value".to_string()), + }; + let end = cursor.checked_add(length).ok_or("Navicat value length overflow")?; + let value = data.get(*cursor..end).ok_or("Truncated Navicat value")?.to_vec(); + *cursor = end; + Ok(value) +} + +fn read_u32_be(data: &[u8], cursor: &mut usize) -> Result { + let end = cursor.checked_add(4).ok_or("Navicat response offset overflow")?; + let bytes: [u8; 4] = data.get(*cursor..end).ok_or("Truncated Navicat integer")?.try_into().unwrap(); + *cursor = end; + Ok(u32::from_be_bytes(bytes)) +} + +fn parse_use_database(sql: &str) -> Option { + let trimmed = sql.trim().trim_end_matches(';').trim(); + let rest = trimmed.get(..4)?.eq_ignore_ascii_case("USE ").then(|| trimmed[4..].trim())?; + let rest = rest.strip_prefix('`').and_then(|value| value.strip_suffix('`')).unwrap_or(rest); + (!rest.is_empty()).then(|| rest.replace("``", "`")) +} + +async fn write_query_result(stream: &mut TcpStream, result: NavicatResult) -> Result<(), String> { + let mut sequence = 1u8; + if result.columns.is_empty() { + write_packet(stream, sequence, &mysql_ok_packet(result.affected_rows, result.last_insert_id, &result.info)) + .await?; + return Ok(()); + } + let mut header = Vec::new(); + push_lenenc(&mut header, result.columns.len() as u64); + write_packet(stream, sequence, &header).await?; + sequence = sequence.wrapping_add(1); + for column in &result.columns { + let definition = mysql_column_packet(column); + write_packet(stream, sequence, &definition).await?; + sequence = sequence.wrapping_add(1); + } + write_packet(stream, sequence, &[0xfe, 0, 0, 2, 0]).await?; + sequence = sequence.wrapping_add(1); + for row in result.rows { + let mut payload = Vec::new(); + for value in row { + match value { + Some(value) => push_lenenc_bytes(&mut payload, &value), + None => payload.push(0xfb), + } + } + write_packet(stream, sequence, &payload).await?; + sequence = sequence.wrapping_add(1); + } + write_packet(stream, sequence, &[0xfe, 0, 0, 2, 0]).await +} + +fn mysql_column_packet(column: &NavicatColumn) -> Vec { + let mut packet = Vec::new(); + for value in [ + b"def".as_slice(), + b"".as_slice(), + column.table.as_slice(), + column.table.as_slice(), + column.name.as_slice(), + column.name.as_slice(), + ] { + push_lenenc_bytes(&mut packet, value); + } + packet.push(0x0c); + packet.extend_from_slice(&45u16.to_le_bytes()); + packet.extend_from_slice(&column.length.to_le_bytes()); + packet.push(column.column_type); + packet.extend_from_slice(&(column.flags as u16).to_le_bytes()); + packet.push(0); + packet.extend_from_slice(&[0, 0]); + packet +} + +fn mysql_ok_packet(affected_rows: u64, last_insert_id: u64, info: &str) -> Vec { + let mut packet = vec![0]; + push_lenenc(&mut packet, affected_rows); + push_lenenc(&mut packet, last_insert_id); + packet.extend_from_slice(&2u16.to_le_bytes()); + packet.extend_from_slice(&0u16.to_le_bytes()); + packet.extend_from_slice(info.as_bytes()); + packet +} + +async fn send_mysql_error(stream: &mut TcpStream, sequence: u8, code: u16, message: &str) -> Result<(), String> { + let mut payload = vec![0xff]; + payload.extend_from_slice(&code.to_le_bytes()); + payload.extend_from_slice(b"#HY000"); + payload.extend_from_slice(message.as_bytes()); + write_packet(stream, sequence, &payload).await +} + +async fn read_packet(stream: &mut TcpStream) -> Result<(u8, Vec), String> { + let mut header = [0; 4]; + match stream.read_exact(&mut header).await { + Ok(_) => {} + Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => { + return Err("MySQL client disconnected".to_string()) + } + Err(error) => return Err(format!("Failed to read MySQL packet header: {error}")), + } + let length = header[0] as usize | ((header[1] as usize) << 8) | ((header[2] as usize) << 16); + if length > MAX_PACKET_SIZE { + return Err("MySQL packet exceeds the supported size".to_string()); + } + let mut payload = vec![0; length]; + stream.read_exact(&mut payload).await.map_err(|error| format!("Failed to read MySQL packet: {error}"))?; + Ok((header[3], payload)) +} + +async fn write_packet(stream: &mut TcpStream, sequence: u8, payload: &[u8]) -> Result<(), String> { + if payload.len() > 0x00ff_ffff { + return Err("MySQL response packet exceeds 16 MiB".to_string()); + } + let length = payload.len(); + let header = [length as u8, (length >> 8) as u8, (length >> 16) as u8, sequence]; + stream.write_all(&header).await.map_err(|error| format!("Failed to write MySQL packet header: {error}"))?; + stream.write_all(payload).await.map_err(|error| format!("Failed to write MySQL packet: {error}")) +} + +fn push_lenenc(output: &mut Vec, value: u64) { + match value { + 0..=250 => output.push(value as u8), + 251..=0xffff => { + output.push(0xfc); + output.extend_from_slice(&(value as u16).to_le_bytes()); + } + 0x1_0000..=0xff_ffff => { + output.push(0xfd); + output.extend_from_slice(&(value as u32).to_le_bytes()[..3]); + } + _ => { + output.push(0xfe); + output.extend_from_slice(&value.to_le_bytes()); + } + } +} + +fn push_lenenc_bytes(output: &mut Vec, value: &[u8]) { + push_lenenc(output, value.len() as u64); + output.extend_from_slice(value); +} + +#[cfg(test)] +mod tests { + use super::{ + parse_navicat_response, parse_use_database, push_lenenc_bytes, read_packet, validate_stateless_query, + write_packet, NavicatMysqlEndpoint, CLIENT_PROTOCOL_41, CLIENT_SECURE_CONNECTION, + }; + use tokio::net::{TcpListener, TcpStream}; + + #[test] + fn accepts_http_and_https_tunnel_urls_without_changing_scheme() { + for scheme in ["http", "https"] { + let endpoint = NavicatMysqlEndpoint::new( + &format!("{scheme}://example.test/ntunnel_mysql.php"), + "127.0.0.1", + 3306, + "user", + "password", + None, + 10, + ) + .unwrap(); + + assert_eq!(endpoint.url.scheme(), scheme); + } + } + + #[test] + fn parses_navicat_query_response() { + let mut body = Vec::from([0, 0, 4, 87, 0, 204, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + body.extend_from_slice(&[0; 4]); + body.extend_from_slice(&[0, 0, 0, 1]); + body.extend_from_slice(&[0; 4]); + body.extend_from_slice(&[0, 0, 0, 1]); + body.extend_from_slice(&[0, 0, 0, 1]); + body.extend_from_slice(&[0; 12]); + push_lenenc_bytes(&mut body, b"id"); + push_lenenc_bytes(&mut body, b"users"); + body.extend_from_slice(&3u32.to_be_bytes()); + body.extend_from_slice(&2u32.to_be_bytes()); + body.extend_from_slice(&11u32.to_be_bytes()); + push_lenenc_bytes(&mut body, b"42"); + body.push(0); + + let result = parse_navicat_response(&body).unwrap(); + assert_eq!(result.columns.len(), 1); + assert_eq!(result.columns[0].name, b"id"); + assert_eq!(result.rows, vec![vec![Some(b"42".to_vec())]]); + } + + #[test] + fn parses_use_database_statement() { + assert_eq!(parse_use_database("USE `other``db`;"), Some("other`db".to_string())); + assert_eq!(parse_use_database("SELECT 1"), None); + } + + #[test] + fn rejects_transaction_control_including_comments_and_autocommit() { + for sql in [ + "BEGIN", + "START /* comment */ TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY", + "COMMIT", + "ROLLBACK TO SAVEPOINT s", + "SAVEPOINT s", + "RELEASE SAVEPOINT s", + "XA START 'x'", + "SET autocommit = 0", + "SET @@autocommit = 0", + "SET @@`autocommit` = 0", + "SET @@session.autocommit = OFF", + "SET SESSION `autocommit` = 0", + "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ", + "/*!40101 SET AUTOCOMMIT=0 */", + "/*M! SET autocommit=0 */", + "START /*!40100 TRANSACTION */", + "SELECT 1; BEGIN", + ] { + assert!(validate_stateless_query(sql).is_err(), "{sql}"); + } + for sql in [ + "SELECT 'BEGIN; SET autocommit=0'", + "SELECT `autocommit` FROM t", + "/* BEGIN */ SELECT 1", + "SET NAMES utf8mb4", + "SET SESSION group_concat_max_len = 1048576", + "SET @note = 'autocommit'", + "USE information_schema", + ] { + assert!(validate_stateless_query(sql).is_ok(), "{sql}"); + } + } + + #[tokio::test] + async fn transaction_commands_fail_before_any_http_request() { + let http_listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = format!("http://{}/ntunnel_mysql.php", http_listener.local_addr().unwrap()); + let endpoint = NavicatMysqlEndpoint::new(&url, "localhost", 3306, "user", "password", None, 1).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let mut client = TcpStream::connect(listener.local_addr().unwrap()).await.unwrap(); + let server = tokio::spawn(super::serve(listener, endpoint)); + + read_packet(&mut client).await.unwrap(); + let mut handshake = [0; 32]; + handshake[..4].copy_from_slice(&CLIENT_PROTOCOL_41.to_le_bytes()); + write_packet(&mut client, 1, &handshake).await.unwrap(); + assert_eq!(read_packet(&mut client).await.unwrap().1[0], 0); + + for sql in ["START TRANSACTION", "BEGIN", "ROLLBACK", "COMMIT", "SET autocommit=0", "SET @@autocommit=0"] { + let response = send_test_command(&mut client, 0x03, sql.as_bytes()).await; + assert_eq!(response[0], 0xff, "{sql}"); + assert_eq!(u16::from_le_bytes(response[1..3].try_into().unwrap()), 1235, "{sql}"); + assert!(String::from_utf8_lossy(&response[9..]).contains("separate MySQL session")); + } + assert!(tokio::time::timeout(std::time::Duration::from_millis(50), http_listener.accept()).await.is_err()); + assert_eq!(send_test_command(&mut client, 0x0e, b"").await[0], 0); + server.abort(); + } + + #[tokio::test] + async fn live_navicat_http_tunnel_query_use_ping_and_error_paths() { + let (Some(url), Some(username), Some(password)) = ( + std::env::var("DBX_NAVICAT_TEST_URL").ok(), + std::env::var("DBX_NAVICAT_TEST_USERNAME").ok(), + std::env::var("DBX_NAVICAT_TEST_PASSWORD").ok(), + ) else { + eprintln!("skipping live Navicat tunnel test: DBX_NAVICAT_TEST_* variables are not all set"); + return; + }; + + let endpoint = NavicatMysqlEndpoint::new(&url, "localhost", 3306, &username, &password, None, 20).unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + let server = tokio::spawn(super::serve(listener, endpoint)); + let mut client = TcpStream::connect(address).await.unwrap(); + + let (_, greeting) = read_packet(&mut client).await.unwrap(); + assert_eq!(greeting.first(), Some(&10), "expected MySQL protocol 10 greeting"); + + let mut handshake = Vec::new(); + handshake.extend_from_slice(&(CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION).to_le_bytes()); + handshake.extend_from_slice(&0x0100_0000u32.to_le_bytes()); + handshake.push(45); + handshake.extend_from_slice(&[0; 23]); + handshake.extend_from_slice(b"dbx-live-test\0"); + handshake.push(0); + write_packet(&mut client, 1, &handshake).await.unwrap(); + let (_, auth_result) = read_packet(&mut client).await.unwrap(); + assert_eq!(auth_result.first(), Some(&0), "expected local proxy authentication OK"); + + let use_result = send_test_command(&mut client, 0x03, b"USE information_schema").await; + assert_eq!(use_result.first(), Some(&0), "USE should return an OK packet"); + + let ping_result = send_test_command(&mut client, 0x0e, b"").await; + assert_eq!(ping_result.first(), Some(&0), "COM_PING should return an OK packet"); + + let _ = send_test_command(&mut client, 0x03, b"SELECT DATABASE()").await; + let (_, column_definition) = read_packet(&mut client).await.unwrap(); + assert!(!column_definition.is_empty()); + let (_, column_terminator) = read_packet(&mut client).await.unwrap(); + assert_eq!(column_terminator.first(), Some(&0xfe)); + let (_, row) = read_packet(&mut client).await.unwrap(); + assert_eq!(row.first().copied(), Some("information_schema".len() as u8)); + assert_eq!(&row[1..], b"information_schema", "USE must select the database for the following HTTP query"); + let (_, row_terminator) = read_packet(&mut client).await.unwrap(); + assert_eq!(row_terminator.first(), Some(&0xfe)); + + let sql_error = send_test_command(&mut client, 0x03, b"SELECT FROM dbx_live_test").await; + assert_eq!(sql_error.first(), Some(&0xff), "invalid SQL should return an ERR packet"); + assert_eq!(u16::from_le_bytes(sql_error[1..3].try_into().unwrap()), 1105); + assert!( + String::from_utf8_lossy(&sql_error[9..]).contains("1064"), + "the Navicat server error should be preserved" + ); + + write_packet(&mut client, 0, &[0x01]).await.unwrap(); + server.abort(); + } + + async fn send_test_command(client: &mut TcpStream, command: u8, payload: &[u8]) -> Vec { + let mut packet = vec![command]; + packet.extend_from_slice(payload); + write_packet(client, 0, &packet).await.unwrap(); + read_packet(client).await.unwrap().1 + } +} diff --git a/crates/dbx-drivers/src/db/transport_layer_tunnel.rs b/crates/dbx-drivers/src/db/transport_layer_tunnel.rs index 97a44d7d6d..7c93d0f453 100644 --- a/crates/dbx-drivers/src/db/transport_layer_tunnel.rs +++ b/crates/dbx-drivers/src/db/transport_layer_tunnel.rs @@ -440,6 +440,7 @@ mod tests { enabled: true, url: url.to_string(), token: String::new(), + protocol: dbx_types::models::connection::HttpTunnelProtocol::DbxTcp, connect_timeout_secs: 10, }) } diff --git a/crates/dbx-types/src/models/connection.rs b/crates/dbx-types/src/models/connection.rs index 89bcece7c5..6006f718cb 100644 --- a/crates/dbx-types/src/models/connection.rs +++ b/crates/dbx-types/src/models/connection.rs @@ -572,6 +572,8 @@ pub struct HttpTunnelConfig { pub url: String, #[serde(default)] pub token: String, + #[serde(default)] + pub protocol: HttpTunnelProtocol, #[serde(default = "default_http_tunnel_connect_timeout_secs")] pub connect_timeout_secs: u64, /// See [`SshTunnelConfig::profile_id`]. @@ -579,6 +581,14 @@ pub struct HttpTunnelConfig { pub profile_id: String, } +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum HttpTunnelProtocol { + #[default] + DbxTcp, + NavicatMysql, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct AttachedDatabaseConfig { pub name: String, diff --git a/deploy/NAVICAT_HTTP_TUNNEL.md b/deploy/NAVICAT_HTTP_TUNNEL.md new file mode 100644 index 0000000000..38ae6c335c --- /dev/null +++ b/deploy/NAVICAT_HTTP_TUNNEL.md @@ -0,0 +1,28 @@ +# Navicat MySQL HTTP Tunnel + +DBX supports Navicat's MySQL HTTP tunnel protocol. DBX does not include or +modify PremiumSoft's proprietary `ntunnel_mysql.php` script. Navicat documents +that the original script is supplied in its installation folder/package on +Windows and can be exported from the HTTP tab on macOS and Linux: +https://help.navicat.com/hc/en-us/articles/218283457-Where-can-I-get-the-HTTP-tunnel-script-file + +The Navicat EULA restricts copying, modification, and distribution except as +expressly allowed by its terms. Obtain the script from an authorized Navicat +distribution and follow the EULA for your edition: +https://www.navicat.com/manual/pdf_manual/en/navicat_17/win_manual/navicat_en.pdf + +The separate `deploy/dbx_tunnel.php` file is DBX's own tunnel and is not a +Navicat-compatible endpoint. + +Upload your authorized copy of `ntunnel_mysql.php` to the PHP host and configure +DBX with its full URL, such as `https://db.example.com/ntunnel_mysql.php`. Plain +HTTP is accepted for trusted private networks, but it exposes database +credentials and SQL in transit. If the endpoint redirects HTTP to HTTPS, +configure DBX with the final HTTPS URL; redirects are not followed because they +can change a POST request into a GET. + +This protocol supports regular queries, `USE`, ping, and disconnect. MySQL TLS +and prepared statements are not supported by the Navicat HTTP tunnel protocol. +Each HTTP request opens a separate MySQL session. Transactions (including manual +transactions and consistent backup snapshots), savepoints, and autocommit +settings are rejected before any SQL is sent to the PHP host. diff --git a/deploy/dbx_tunnel.php b/deploy/dbx_tunnel.php index 69706e7460..1b95e8dc78 100644 --- a/deploy/dbx_tunnel.php +++ b/deploy/dbx_tunnel.php @@ -65,6 +65,11 @@ try { function handle_open(): void { + $workerStartMethod = worker_start_method(); + if ($workerStartMethod === null) { + respond_error(503, 'HTTP tunnel is unsupported on this PHP host: it requires PHP-FPM fastcgi_finish_request() or permission to spawn PHP CLI with popen()'); + } + $session = request_session(); $host = request_param('dbx_target_host'); $port = (int) request_param('dbx_target_port'); @@ -83,7 +88,7 @@ function handle_open(): void touch($dir . DIRECTORY_SEPARATOR . 'in.queue'); touch($dir . DIRECTORY_SEPARATOR . 'out.queue'); - if (function_exists('fastcgi_finish_request')) { + if ($workerStartMethod === 'fastcgi') { respond_json(['ok' => true], false); fastcgi_finish_request(); run_worker($dir, $host, $port, $connectTimeout); @@ -97,7 +102,23 @@ function handle_open(): void respond_error(500, 'PHP tunnel worker did not start'); } - respond_error(500, 'PHP tunnel requires PHP-FPM fastcgi_finish_request or permission to spawn PHP CLI'); + respond_error(503, 'HTTP tunnel worker could not be started with popen()'); +} + +function worker_start_method(): ?string +{ + return select_worker_start_method(function_exists('fastcgi_finish_request'), function_exists('popen')); +} + +function select_worker_start_method(bool $hasFastcgiFinishRequest, bool $hasPopen): ?string +{ + if ($hasFastcgiFinishRequest) { + return 'fastcgi'; + } + if ($hasPopen) { + return 'popen'; + } + return null; } function handle_write(): void diff --git a/deploy/tests/http_tunnel_worker_test.php b/deploy/tests/http_tunnel_worker_test.php index a273932a48..9711530e25 100644 --- a/deploy/tests/http_tunnel_worker_test.php +++ b/deploy/tests/http_tunnel_worker_test.php @@ -4,6 +4,9 @@ declare(strict_types=1); define('DBX_TUNNEL_FUNCTIONS_ONLY', true); require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'dbx_tunnel.php'; +assert_same('fastcgi', select_worker_start_method(true, false), 'PHP-FPM can keep the request worker alive'); +assert_same('popen', select_worker_start_method(false, true), 'PHP CLI can be spawned when PHP-FPM is unavailable'); +assert_same(null, select_worker_start_method(false, false), 'CGI/FastCGI without process spawning cannot run the tunnel worker'); assert_same(10000, worker_poll_timeout_us(0), 'active polling starts at 10ms'); assert_same(10000, worker_poll_timeout_us(99), 'active polling lasts for 100 idle polls'); assert_same(50000, worker_poll_timeout_us(100), 'brief inactivity backs off to 50ms'); diff --git a/docs/content/docs/ssh-tunnel.cn.mdx b/docs/content/docs/ssh-tunnel.cn.mdx index f53b0e968d..d8e117e35c 100644 --- a/docs/content/docs/ssh-tunnel.cn.mdx +++ b/docs/content/docs/ssh-tunnel.cn.mdx @@ -102,6 +102,8 @@ DBX 支持: 当数据库在内网中,外部只能访问一台内网 Web 服务器,并且不能使用 SSH/VPN 时,可以使用 HTTP 隧道。这类场景和 Navicat、SQL-Front 的 HTTP 隧道类似。 +PHP 脚本需要 PHP-FPM 的 `fastcgi_finish_request()`,或允许通过 `popen()` 启动 PHP CLI。若 CGI/FastCGI 共享主机不具备这两种能力,就无法运行此 TCP 隧道;脚本会返回 HTTP 503 和具体原因。目前没有提供 SQL 级 MySQL HTTP 适配器。 + ### 部署脚本 把 `deploy/dbx_tunnel.php` 上传到一台能访问目标数据库的 PHP Web 服务器。 @@ -114,7 +116,7 @@ DBX 支持: | `DBX_TUNNEL_ALLOWED_HOSTS` | 否 | 允许连接的目标主机列表,逗号分隔,强烈建议配置。 | | `DBX_TUNNEL_DIR` | 否 | 会话队列目录,默认使用临时目录。 | | `DBX_TUNNEL_MAX_SESSION_SECONDS` | 否 | 会话最长生命周期,默认 `3600` 秒。 | -| `DBX_TUNNEL_PHP` | 否 | 当 PHP-FPM 没有 `fastcgi_finish_request` 时,用于启动 worker 的 PHP CLI 路径。 | +| `DBX_TUNNEL_PHP` | 否 | PHP-FPM 没有 `fastcgi_finish_request()` 且 `popen()` 可用时,用于启动 worker 的 PHP CLI 路径。 | 在 DBX 里添加 **HTTP 隧道** 层,并填写: diff --git a/docs/content/docs/ssh-tunnel.mdx b/docs/content/docs/ssh-tunnel.mdx index a0fd778799..9440c35688 100644 --- a/docs/content/docs/ssh-tunnel.mdx +++ b/docs/content/docs/ssh-tunnel.mdx @@ -102,6 +102,8 @@ A shared proxy profile may include an optional test target such as `mysql.intern Use HTTP tunnel when the database is inside a private network, but a web server in that network can reach it. This matches environments where only HTTP/HTTPS is available and SSH tunnel permissions are too broad or unavailable. +The PHP script needs either PHP-FPM's `fastcgi_finish_request()` or permission to spawn PHP CLI with `popen()`. CGI/FastCGI shared hosting with both capabilities unavailable cannot run this TCP tunnel; the script returns HTTP 503 with the reason. A SQL-level MySQL HTTP adapter is not provided. + ### Deploy The Script Upload `deploy/dbx_tunnel.php` to a PHP web server that can reach the database. @@ -114,7 +116,7 @@ Configure these environment variables on the PHP server: | `DBX_TUNNEL_ALLOWED_HOSTS` | No | Comma-separated target host allow-list. Strongly recommended. | | `DBX_TUNNEL_DIR` | No | Session queue directory. Defaults to a temp directory. | | `DBX_TUNNEL_MAX_SESSION_SECONDS` | No | Maximum session lifetime. Defaults to `3600`. | -| `DBX_TUNNEL_PHP` | No | PHP CLI path used when PHP-FPM `fastcgi_finish_request` is not available. | +| `DBX_TUNNEL_PHP` | No | PHP CLI path used when PHP-FPM `fastcgi_finish_request()` is not available and `popen()` is allowed. | In DBX, add an **HTTP Tunnel** layer and fill: