Skip to content

Commit 898c34c

Browse files
committed
fix clientIp message interception
1 parent dfd9bd5 commit 898c34c

File tree

8 files changed

+126
-43
lines changed

8 files changed

+126
-43
lines changed
Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,48 @@
1-
export function createParameterStatusMessage(name: string, value: string): ArrayBuffer {
1+
export function createStartupMessage(
2+
user: string,
3+
database: string,
4+
additionalParams: Record<string, string> = {}
5+
): ArrayBuffer {
26
const encoder = new TextEncoder()
3-
const nameBuffer = encoder.encode(name + '\0')
4-
const valueBuffer = encoder.encode(value + '\0')
57

6-
const messageLength = 4 + nameBuffer.length + valueBuffer.length
7-
const message = new ArrayBuffer(1 + messageLength)
8+
// Protocol version number (3.0)
9+
const protocolVersion = 196608
10+
11+
// Combine required and additional parameters
12+
const params = {
13+
user,
14+
database,
15+
...additionalParams,
16+
}
17+
18+
// Calculate total message length
19+
let messageLength = 4 // Protocol version
20+
for (const [key, value] of Object.entries(params)) {
21+
messageLength += key.length + 1 + value.length + 1
22+
}
23+
messageLength += 1 // Null terminator
24+
25+
const message = new ArrayBuffer(4 + messageLength)
826
const view = new DataView(message)
927
const uint8Array = new Uint8Array(message)
1028

1129
let offset = 0
12-
view.setUint8(offset++, 'S'.charCodeAt(0)) // Message type
13-
view.setUint32(offset, messageLength, false) // Message length (big-endian)
30+
view.setInt32(offset, messageLength + 4, false) // Total message length (including itself)
31+
offset += 4
32+
view.setInt32(offset, protocolVersion, false) // Protocol version number
1433
offset += 4
1534

16-
uint8Array.set(nameBuffer, offset)
17-
offset += nameBuffer.length
35+
// Write key-value pairs
36+
for (const [key, value] of Object.entries(params)) {
37+
uint8Array.set(encoder.encode(key), offset)
38+
offset += key.length
39+
uint8Array.set([0], offset++) // Null terminator for key
40+
uint8Array.set(encoder.encode(value), offset)
41+
offset += value.length
42+
uint8Array.set([0], offset++) // Null terminator for value
43+
}
1844

19-
uint8Array.set(valueBuffer, offset)
45+
uint8Array.set([0], offset) // Final null terminator
2046

2147
return message
2248
}

apps/browser-proxy/src/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import makeDebug from 'debug'
66
import * as tls from 'node:tls'
77
import { extractDatabaseId, isValidServername } from './servername.ts'
88
import { getTls } from './tls.ts'
9-
import { createParameterStatusMessage } from './create-message.ts'
9+
import { createStartupMessage } from './create-message.ts'
1010
import { extractIP } from './extract-ip.ts'
1111

1212
const debug = makeDebug('browser-proxy')
@@ -147,10 +147,9 @@ tcpServer.on('connection', (socket) => {
147147
return
148148
}
149149

150-
const clientIpMessage = createParameterStatusMessage(
151-
'client_ip',
152-
extractIP(socket.remoteAddress!)
153-
)
150+
const clientIpMessage = createStartupMessage('postgres', 'postgres', {
151+
client_ip: extractIP(connection.socket.remoteAddress!),
152+
})
154153
websocket.send(clientIpMessage)
155154
},
156155
onMessage(message, state) {
@@ -182,7 +181,7 @@ tcpServer.on('connection', (socket) => {
182181
if (databaseId) {
183182
tcpConnections.delete(databaseId)
184183
const websocket = websocketConnections.get(databaseId)
185-
websocket?.send(createParameterStatusMessage('client_ip', ''))
184+
websocket?.send(createStartupMessage('postgres', 'postgres', { client_ip: '' }))
186185
}
187186
})
188187
})

apps/postgres-new/components/app-provider.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from 'react'
1818
import { DbManager } from '~/lib/db'
1919
import { useAsyncMemo } from '~/lib/hooks'
20-
import { parseParameterStatus } from '~/lib/pg-wire-util'
20+
import { isStartupMessage, parseStartupMessage } from '~/lib/pg-wire-util'
2121
import { createClient } from '~/utils/supabase/client'
2222

2323
export type AppProps = PropsWithChildren
@@ -131,19 +131,20 @@ export default function AppProvider({ children }: AppProps) {
131131
ws.onopen = () => {
132132
setLiveSharedDatabaseId(databaseId)
133133
}
134+
134135
ws.onmessage = async (event) => {
135136
const message = new Uint8Array(await event.data)
136137

137-
const messageType = String.fromCharCode(message[0])
138-
if (messageType === 'S') {
139-
const { name, value } = parseParameterStatus(message)
140-
if (name === 'client_ip') {
141-
setConnectedClientIp(value === '' ? null : value)
142-
return
138+
if (isStartupMessage(message)) {
139+
const parameters = parseStartupMessage(message)
140+
if ('client_ip' in parameters) {
141+
setConnectedClientIp(parameters.client_ip === '' ? null : parameters.client_ip)
143142
}
143+
return
144144
}
145145

146146
const response = await db.execProtocolRaw(message)
147+
147148
ws.send(response)
148149
}
149150
ws.onclose = (event) => {
5.27 MB
Binary file not shown.
Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,80 @@
1-
export function parseParameterStatus(data: Uint8Array): { name: string; value: string } {
1+
export function isStartupMessage(message: Uint8Array): boolean {
2+
if (message.length < 8) {
3+
return false // Message is too short to be a valid startup message
4+
}
5+
6+
const view = new DataView(message.buffer, message.byteOffset, message.byteLength)
7+
8+
// Get the message length (first 4 bytes)
9+
const messageLength = view.getInt32(0, false) // big-endian
10+
11+
// Check if the message length matches the actual buffer length
12+
if (messageLength !== message.length) {
13+
return false
14+
}
15+
16+
// Get the protocol version (next 4 bytes)
17+
const protocolVersion = view.getInt32(4, false) // big-endian
18+
19+
// Check if the protocol version is valid (3.0)
20+
// Protocol version 3.0 is represented as 196608 (0x00030000)
21+
if (protocolVersion !== 196608) {
22+
return false
23+
}
24+
25+
// Additional check: ensure the message ends with a null terminator
26+
if (message[message.length - 1] !== 0) {
27+
return false
28+
}
29+
30+
return true
31+
}
32+
33+
export function parseStartupMessage(message: Uint8Array): {
34+
user: string
35+
database: string
36+
[key: string]: string
37+
} {
38+
const view = new DataView(message.buffer, message.byteOffset, message.byteLength)
239
const decoder = new TextDecoder()
40+
const params: {
41+
user: string
42+
database: string
43+
[key: string]: string
44+
} = {}
45+
46+
// Skip the message length (4 bytes) and protocol version (4 bytes)
47+
let offset = 8
48+
49+
while (offset < message.length) {
50+
let keyStart = offset
51+
let keyEnd = keyStart
52+
while (message[keyEnd] !== 0 && keyEnd < message.length) {
53+
keyEnd++
54+
}
55+
if (keyEnd === message.length) break // End of message
56+
57+
const key = decoder.decode(message.subarray(keyStart, keyEnd))
58+
offset = keyEnd + 1 // Skip null terminator
359

4-
// Skip message type (1 byte) and length (4 bytes)
5-
let offset = 5
60+
let valueStart = offset
61+
let valueEnd = valueStart
62+
while (message[valueEnd] !== 0 && valueEnd < message.length) {
63+
valueEnd++
64+
}
65+
if (valueEnd === message.length) break // End of message
666

7-
// Find the null terminator for the name
8-
let nameEnd = offset
9-
while (data[nameEnd] !== 0) nameEnd++
67+
const value = decoder.decode(message.subarray(valueStart, valueEnd))
68+
offset = valueEnd + 1 // Skip null terminator
1069

11-
const name = decoder.decode(data.subarray(offset, nameEnd))
12-
offset = nameEnd + 1
70+
params[key] = value
1371

14-
// Find the null terminator for the value
15-
let valueEnd = offset
16-
while (data[valueEnd] !== 0) valueEnd++
72+
if (message[offset] === 0) break // Final null terminator
73+
}
1774

18-
const value = decoder.decode(data.subarray(offset, valueEnd))
75+
if (!params.user || !params.database) {
76+
throw new Error('user or database not found in startup message')
77+
}
1978

20-
return { name, value }
79+
return params
2180
}

apps/postgres-new/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"dependencies": {
1313
"@ai-sdk/openai": "^0.0.21",
1414
"@dagrejs/dagre": "^1.1.2",
15-
"@electric-sql/pglite": "0.2.6",
15+
"@electric-sql/pglite": "file:./electric-sql-pglite-0.2.6.tgz",
1616
"@gregnr/postgres-meta": "^0.82.0-dev.2",
1717
"@monaco-editor/react": "^4.6.0",
1818
"@radix-ui/react-accordion": "^1.2.0",

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
"scripts": {
44
"dev": "npm run dev --workspace postgres-new"
55
},
6-
"workspaces": [
7-
"apps/*"
8-
],
6+
"workspaces": ["apps/*"],
97
"devDependencies": {
108
"supabase": "^1.191.3"
119
}

0 commit comments

Comments
 (0)