ts-postgres
Advanced tools
Comparing version
@@ -69,6 +69,8 @@ /// <reference types="node" /> | ||
private readonly writer; | ||
private readonly clientNonce; | ||
private serverSignature; | ||
private expect; | ||
private stream; | ||
private offset; | ||
private mustDrain; | ||
private activeRow; | ||
private bindQueue; | ||
@@ -75,0 +77,0 @@ private closeHandlerQueue; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Client = void 0; | ||
const crypto_1 = require("crypto"); | ||
const os_1 = require("os"); | ||
@@ -19,3 +20,3 @@ const net_1 = require("net"); | ||
this.config = config; | ||
this.events = (0, ts_typed_events_1.events)({ | ||
this.events = ts_typed_events_1.events({ | ||
connect: new ts_typed_events_1.Event(), | ||
@@ -33,6 +34,8 @@ end: new ts_typed_events_1.Event(), | ||
this.encoding = 'utf-8'; | ||
this.clientNonce = crypto_1.randomBytes(18).toString('base64'); | ||
this.serverSignature = null; | ||
this.expect = 5; | ||
this.stream = new net_1.Socket(); | ||
this.offset = 0; | ||
this.mustDrain = false; | ||
this.activeRow = null; | ||
this.bindQueue = new queue_1.Queue(); | ||
@@ -65,2 +68,3 @@ this.closeHandlerQueue = new queue_1.Queue(); | ||
}); | ||
/* istanbul ignore next */ | ||
this.stream.on('error', (error) => { | ||
@@ -131,3 +135,3 @@ if (this.connecting) { | ||
const context = ssl.options ? | ||
(0, tls_1.createSecureContext)(ssl.options) : | ||
tls_1.createSecureContext(ssl.options) : | ||
undefined; | ||
@@ -139,3 +143,3 @@ const options = { | ||
const verify = ssl.mode == "verify-ca" /* VerifyCA */; | ||
const stream = (0, tls_1.connect)(options, () => { | ||
const stream = tls_1.connect(options, () => { | ||
if (verify && !stream.authorized) { | ||
@@ -161,2 +165,3 @@ abort(stream.authorizationError); | ||
let buffer = null; | ||
let offset = 0; | ||
let remaining = 0; | ||
@@ -167,8 +172,8 @@ this.stream.on('data', (newBuffer) => { | ||
if (buffer && remaining) { | ||
const free = buffer.length - this.offset - remaining; | ||
let tail = this.offset + remaining; | ||
const free = buffer.length - offset - remaining; | ||
let tail = offset + remaining; | ||
if (free < length) { | ||
const tempBuf = Buffer.allocUnsafe(size); | ||
buffer.copy(tempBuf, 0, this.offset, tail); | ||
this.offset = 0; | ||
buffer.copy(tempBuf, 0, offset, tail); | ||
offset = 0; | ||
buffer = tempBuf; | ||
@@ -181,10 +186,13 @@ tail = remaining; | ||
buffer = newBuffer; | ||
this.offset = 0; | ||
offset = 0; | ||
} | ||
try { | ||
const read = this.receive(buffer, this.offset, size); | ||
this.offset += read; | ||
const read = this.receive(buffer, offset, size); | ||
offset += read; | ||
remaining = size - read; | ||
} | ||
catch (error) { | ||
if (this.connecting) { | ||
this.events.connect.emit(error); | ||
} | ||
logger.warn(error); | ||
@@ -211,2 +219,4 @@ this.stream.destroy(); | ||
return; | ||
this.connecting = false; | ||
this.stream.destroy(); | ||
throw error; | ||
@@ -303,3 +313,3 @@ }); | ||
execute: (values, portal, format, streams) => { | ||
const result = (0, result_1.makeResult)(); | ||
const result = result_1.makeResult(); | ||
result.nameHandler(description.names); | ||
@@ -376,3 +386,3 @@ const info = { | ||
const portal = (options ? options.portal : undefined) || ''; | ||
const result = (0, result_1.makeResult)(); | ||
const result = result_1.makeResult(); | ||
const descriptionHandler = (description) => { | ||
@@ -421,3 +431,8 @@ result.nameHandler(description.names); | ||
} | ||
this.errorHandlerQueue.push((error) => result.dataHandler(error)); | ||
const stack = new Error().stack; | ||
this.errorHandlerQueue.push((error) => { | ||
if (stack !== undefined) | ||
error.stack = stack.replace(/(?<=^Error: )\n/, error.toString() + "\n"); | ||
result.dataHandler(error); | ||
}); | ||
this.cleanupQueue.push(2 /* ErrorHandler */); | ||
@@ -495,3 +510,3 @@ this.writer.sync(); | ||
const { handler: { streams, callback, }, description: { columns, names, } } = info; | ||
let row = null; | ||
let row = this.activeRow; | ||
const hasStreams = Object.keys(streams).length > 0; | ||
@@ -507,2 +522,3 @@ const mappedStreams = hasStreams ? names.map(name => streams[name] || null) : null; | ||
this.expect = 7; | ||
this.activeRow = row; | ||
return read; | ||
@@ -516,3 +532,3 @@ } | ||
const slice = buffer.slice(startRowData, bytes + read); | ||
const end = (0, protocol_1.readRowData)(slice, row, columns, this.encoding, types, mappedStreams); | ||
const end = protocol_1.readRowData(slice, row, columns, this.encoding, types, mappedStreams); | ||
const remaining = bytes + read - size; | ||
@@ -528,3 +544,5 @@ if (remaining <= 0) { | ||
buffer.writeInt16BE(row.length, offset - 2); | ||
return offset - 7; | ||
this.expect = 12; | ||
this.activeRow = row; | ||
return read + end; | ||
} | ||
@@ -536,2 +554,3 @@ // Keep track of how much data we've consumed. | ||
if (size < frame + 5) { | ||
this.activeRow = row; | ||
this.expect = 5; | ||
@@ -542,2 +561,3 @@ return read; | ||
} | ||
this.activeRow = null; | ||
} | ||
@@ -558,2 +578,4 @@ const bytes = buffer.readInt32BE(frame + 1) + 1; | ||
const code = buffer.readInt32BE(start); | ||
outer: | ||
/* istanbul ignore next */ | ||
switch (code) { | ||
@@ -574,7 +596,33 @@ case 0: { | ||
const salt = buffer.slice(start + 4, start + 8); | ||
const shadow = (0, utils_1.md5)(`${password || defaults.password}` + | ||
const shadow = utils_1.md5(`${password || defaults.password}` + | ||
`${user || defaults.user}`); | ||
writer.password(`md5${(0, utils_1.md5)(shadow, salt)}`); | ||
writer.password(`md5${utils_1.md5(shadow, salt)}`); | ||
break; | ||
} | ||
case 10: { | ||
const reader = new protocol_1.Reader(buffer, start + 4); | ||
const mechanisms = []; | ||
while (true) { | ||
const mechanism = reader.readCString(this.encoding); | ||
if (mechanism.length === 0) | ||
break; | ||
if (writer.saslInitialResponse(mechanism, this.clientNonce)) | ||
break outer; | ||
mechanisms.push(mechanism); | ||
} | ||
throw new Error(`SASL authentication unsupported (mechanisms: ${mechanisms.join(', ')})`); | ||
} | ||
case 11: { | ||
const data = buffer.slice(start + 4, start + length).toString("utf8"); | ||
const password = this.config.password || defaults.password || ''; | ||
this.serverSignature = writer.saslResponse(data, password, this.clientNonce); | ||
break; | ||
} | ||
case 12: { | ||
const data = buffer.slice(start + 4, start + length).toString("utf8"); | ||
if (!this.serverSignature) | ||
throw new Error('Server signature missing'); | ||
writer.saslFinal(data, this.serverSignature); | ||
break; | ||
} | ||
default: | ||
@@ -603,11 +651,11 @@ throw new Error(`Unsupported authentication scheme: ${code}`); | ||
if (preflight.dataHandler) { | ||
const info = { | ||
handler: preflight.dataHandler, | ||
description: null, | ||
}; | ||
if (preflight.bind) { | ||
const info = { | ||
handler: preflight.dataHandler, | ||
description: null, | ||
}; | ||
this.bindAndExecute(info, preflight.bind, this.parameterDescriptionQueue.shift()); | ||
} | ||
else { | ||
preflight.dataHandler.callback(null); | ||
this.activeDataHandlerInfo = info; | ||
} | ||
@@ -622,7 +670,6 @@ } | ||
case 67 /* CommandComplete */: { | ||
// This is unset if the query had no row data. | ||
const info = this.activeDataHandlerInfo; | ||
if (info) { | ||
const status = buffer.slice(start, start + length - 1).toString(); | ||
info.handler.callback(status); | ||
info.handler.callback(status || null); | ||
this.activeDataHandlerInfo = null; | ||
@@ -642,2 +689,4 @@ } | ||
const error = this.parseError(buffer.slice(start, start + length)); | ||
if (this.connecting) | ||
throw error; | ||
this.events.error.emit(error); | ||
@@ -738,3 +787,3 @@ loop: while (true) { | ||
const preflight = this.preFlightQueue.shift(); | ||
const description = (0, protocol_1.readRowDescription)(buffer, start, this.config.types); | ||
const description = protocol_1.readRowDescription(buffer, start, this.config.types); | ||
preflight.descriptionHandler(description); | ||
@@ -741,0 +790,0 @@ if (preflight.dataHandler) { |
@@ -10,6 +10,5 @@ "use strict"; | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DatabaseError = void 0; | ||
__exportStar(require("./client"), exports); | ||
@@ -16,0 +15,0 @@ __exportStar(require("./types"), exports); |
@@ -16,3 +16,4 @@ /// <reference types="node" /> | ||
Query = 81, | ||
Sync = 83 | ||
Sync = 83, | ||
SASLResponse = 112 | ||
} | ||
@@ -104,2 +105,5 @@ export declare enum ErrorLevel { | ||
password(text: string): void; | ||
saslInitialResponse(mechanism: string, clientNonce: string): boolean; | ||
saslResponse(data: string, password: string, clientNonce: string): string; | ||
saslFinal(data: string, serverSignature: string): void; | ||
send(socket: Socket): boolean; | ||
@@ -106,0 +110,0 @@ startup(config: StartupConfiguration): void; |
@@ -5,2 +5,3 @@ "use strict"; | ||
const buffer_1 = require("./buffer"); | ||
const sasl_1 = require("./sasl"); | ||
const utils_1 = require("./utils"); | ||
@@ -440,3 +441,2 @@ const types_1 = require("./types"); | ||
class Writer { | ||
//private outgoing_msg_debug: Array<number | null> = []; | ||
constructor(encoding) { | ||
@@ -501,6 +501,6 @@ this.encoding = encoding; | ||
if (value === infinity) { | ||
size = (0, utils_1.sum)(add(7 /* UInt32BE */, 0x7fffffff), add(7 /* UInt32BE */, 0xffffffff)); | ||
size = utils_1.sum(add(7 /* UInt32BE */, 0x7fffffff), add(7 /* UInt32BE */, 0xffffffff)); | ||
} | ||
else if (value === -infinity) { | ||
size = (0, utils_1.sum)(add(7 /* UInt32BE */, 0x80000000), add(7 /* UInt32BE */, 0x00000000)); | ||
size = utils_1.sum(add(7 /* UInt32BE */, 0x80000000), add(7 /* UInt32BE */, 0x00000000)); | ||
} | ||
@@ -511,3 +511,3 @@ else if (value instanceof Date) { | ||
const r = n - f * 4294967296; | ||
size = (0, utils_1.sum)(add(5 /* Int32BE */, f), add(7 /* UInt32BE */, r)); | ||
size = utils_1.sum(add(5 /* Int32BE */, f), add(7 /* UInt32BE */, r)); | ||
} | ||
@@ -553,4 +553,4 @@ break; | ||
case types_1.DataType.Point: { | ||
if ((0, types_1.isPoint)(value)) { | ||
size = (0, utils_1.sum)(add(2 /* Float8 */, value.x), add(2 /* Float8 */, value.y)); | ||
if (types_1.isPoint(value)) { | ||
size = utils_1.sum(add(2 /* Float8 */, value.x), add(2 /* Float8 */, value.y)); | ||
} | ||
@@ -605,3 +605,3 @@ break; | ||
if (level === dimCount) { | ||
bytes += (0, utils_1.sum)(add(5 /* Int32BE */, length), add(5 /* Int32BE */, 1)); | ||
bytes += utils_1.sum(add(5 /* Int32BE */, length), add(5 /* Int32BE */, 1)); | ||
dimCount++; | ||
@@ -711,3 +711,3 @@ } | ||
const size = (result instanceof Array) ? | ||
(0, utils_1.sum)(...result.map((s) => add(0 /* Buffer */, makeBuffer(s, this.encoding)))) : | ||
utils_1.sum(...result.map((s) => add(0 /* Buffer */, makeBuffer(s, this.encoding)))) : | ||
add(0 /* Buffer */, (result === null) ? | ||
@@ -760,2 +760,54 @@ nullBuffer : | ||
} | ||
saslInitialResponse(mechanism, clientNonce) { | ||
if (mechanism !== 'SCRAM-SHA-256') | ||
return false; | ||
const response = Buffer.from('n,,n=*,r=' + clientNonce); | ||
this.enqueue(112 /* SASLResponse */, [ | ||
makeBufferSegment(mechanism, this.encoding, true), | ||
[5 /* Int32BE */, response.length], | ||
[0 /* Buffer */, response] | ||
]); | ||
return true; | ||
} | ||
saslResponse(data, password, clientNonce) { | ||
const m = Object.fromEntries(data.split(',').map((attr) => [attr[0], attr.substring(2)])); | ||
if (!(m.i && m.r && m.s)) | ||
throw new Error("SASL message parse error"); | ||
const nonce = m.r; | ||
if (!nonce.startsWith(clientNonce)) | ||
throw new Error("SASL nonce mismatch"); | ||
if (nonce.length === clientNonce.length) | ||
throw new Error("SASL nonce too short"); | ||
const iterations = parseInt(m.i, 10); | ||
const salt = Buffer.from(m.s, 'base64'); | ||
const saltedPassword = sasl_1.hi(password, salt, iterations); | ||
const clientKey = sasl_1.hmacSha256(saltedPassword, 'Client Key'); | ||
const storedKey = sasl_1.sha256(clientKey); | ||
const clientFinalMessageWithoutProof = 'c=biws,r=' + nonce; | ||
const clientFirstMessageBare = 'n=*,r=' + clientNonce; | ||
const serverFirstMessage = data; | ||
const authMessage = (clientFirstMessageBare + ',' + | ||
serverFirstMessage + ',' + | ||
clientFinalMessageWithoutProof); | ||
const clientSignature = sasl_1.hmacSha256(storedKey, authMessage); | ||
const clientProofBytes = sasl_1.xorBuffers(clientKey, clientSignature); | ||
const clientProof = clientProofBytes.toString('base64'); | ||
const serverKey = sasl_1.hmacSha256(saltedPassword, 'Server Key'); | ||
const serverSignatureBytes = sasl_1.hmacSha256(serverKey, authMessage); | ||
const response = clientFinalMessageWithoutProof + ',p=' + clientProof; | ||
const serverSignature = serverSignatureBytes.toString('base64'); | ||
this.enqueue(112 /* SASLResponse */, [ | ||
makeBufferSegment(response, this.encoding, false) | ||
]); | ||
return serverSignature; | ||
} | ||
saslFinal(data, serverSignature) { | ||
if (!data.split(',').find((attr) => { | ||
if (attr[0] === 'v') { | ||
return (attr.substr(2) === serverSignature); | ||
} | ||
return false; | ||
})) | ||
throw new Error('SASL server signature does not match'); | ||
} | ||
send(socket) { | ||
@@ -762,0 +814,0 @@ if (this.outgoing.empty) |
"use strict"; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); | ||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) { | ||
if (!privateMap.has(receiver)) { | ||
throw new TypeError("attempted to get private field on non-instance"); | ||
} | ||
return privateMap.get(receiver); | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
if (kind === "m") throw new TypeError("Private method is not writable"); | ||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); | ||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); | ||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) { | ||
if (!privateMap.has(receiver)) { | ||
throw new TypeError("attempted to set private field on non-instance"); | ||
} | ||
privateMap.set(receiver, value); | ||
return value; | ||
}; | ||
var _Queue_head, _Queue_tail, _Queue_capacityMask, _Queue_list; | ||
var _head, _tail, _capacityMask, _list; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -19,35 +21,35 @@ exports.Queue = void 0; | ||
constructor() { | ||
_Queue_head.set(this, 0); | ||
_Queue_tail.set(this, 0); | ||
_Queue_capacityMask.set(this, 0b11); | ||
_Queue_list.set(this, new Array(__classPrivateFieldGet(this, _Queue_capacityMask, "f") + 1)); | ||
_head.set(this, 0); | ||
_tail.set(this, 0); | ||
_capacityMask.set(this, 0b11); | ||
_list.set(this, new Array(__classPrivateFieldGet(this, _capacityMask) + 1)); | ||
} | ||
/** Returns the capacity of the queue. That is, that of the inner buffer. */ | ||
get capacity() { | ||
return __classPrivateFieldGet(this, _Queue_list, "f").length; | ||
return __classPrivateFieldGet(this, _list).length; | ||
} | ||
/** Returns the current number of elements in the queue. */ | ||
get length() { | ||
return __classPrivateFieldGet(this, _Queue_head, "f") <= __classPrivateFieldGet(this, _Queue_tail, "f") | ||
? __classPrivateFieldGet(this, _Queue_tail, "f") - __classPrivateFieldGet(this, _Queue_head, "f") | ||
: __classPrivateFieldGet(this, _Queue_capacityMask, "f") + 1 - (__classPrivateFieldGet(this, _Queue_head, "f") - __classPrivateFieldGet(this, _Queue_tail, "f")); | ||
return __classPrivateFieldGet(this, _head) <= __classPrivateFieldGet(this, _tail) | ||
? __classPrivateFieldGet(this, _tail) - __classPrivateFieldGet(this, _head) | ||
: __classPrivateFieldGet(this, _capacityMask) + 1 - (__classPrivateFieldGet(this, _head) - __classPrivateFieldGet(this, _tail)); | ||
} | ||
/** Returns whether the deque is empty. */ | ||
get empty() { | ||
return __classPrivateFieldGet(this, _Queue_head, "f") === __classPrivateFieldGet(this, _Queue_tail, "f"); | ||
return __classPrivateFieldGet(this, _head) === __classPrivateFieldGet(this, _tail); | ||
} | ||
/** Performs a "soft" clear. This does **not** reset capacity. */ | ||
clear() { | ||
__classPrivateFieldSet(this, _Queue_head, __classPrivateFieldSet(this, _Queue_tail, 0, "f"), "f"); | ||
__classPrivateFieldSet(this, _head, __classPrivateFieldSet(this, _tail, 0)); | ||
} | ||
/** Inserts item to first slot. Returns the new length of the deque. */ | ||
unshift(item) { | ||
const len = __classPrivateFieldGet(this, _Queue_list, "f").length; | ||
__classPrivateFieldSet(this, _Queue_head, (__classPrivateFieldGet(this, _Queue_head, "f") - 1 + len) & __classPrivateFieldGet(this, _Queue_capacityMask, "f"), "f"); | ||
__classPrivateFieldGet(this, _Queue_list, "f")[__classPrivateFieldGet(this, _Queue_head, "f")] = item; | ||
if (__classPrivateFieldGet(this, _Queue_tail, "f") === __classPrivateFieldGet(this, _Queue_head, "f")) | ||
const len = __classPrivateFieldGet(this, _list).length; | ||
__classPrivateFieldSet(this, _head, (__classPrivateFieldGet(this, _head) - 1 + len) & __classPrivateFieldGet(this, _capacityMask)); | ||
__classPrivateFieldGet(this, _list)[__classPrivateFieldGet(this, _head)] = item; | ||
if (__classPrivateFieldGet(this, _tail) === __classPrivateFieldGet(this, _head)) | ||
this.growArray(); | ||
if (__classPrivateFieldGet(this, _Queue_head, "f") < __classPrivateFieldGet(this, _Queue_tail, "f")) | ||
return __classPrivateFieldGet(this, _Queue_tail, "f") - __classPrivateFieldGet(this, _Queue_head, "f"); | ||
return __classPrivateFieldGet(this, _Queue_capacityMask, "f") + 1 - (__classPrivateFieldGet(this, _Queue_head, "f") - __classPrivateFieldGet(this, _Queue_tail, "f")); | ||
if (__classPrivateFieldGet(this, _head) < __classPrivateFieldGet(this, _tail)) | ||
return __classPrivateFieldGet(this, _tail) - __classPrivateFieldGet(this, _head); | ||
return __classPrivateFieldGet(this, _capacityMask) + 1 - (__classPrivateFieldGet(this, _head) - __classPrivateFieldGet(this, _tail)); | ||
} | ||
@@ -65,7 +67,7 @@ /** Removes and returns the first element. */ | ||
return; | ||
const head = __classPrivateFieldGet(this, _Queue_head, "f"); | ||
const item = __classPrivateFieldGet(this, _Queue_list, "f")[head]; | ||
__classPrivateFieldGet(this, _Queue_list, "f")[head] = undefined; | ||
__classPrivateFieldSet(this, _Queue_head, (head + 1) & __classPrivateFieldGet(this, _Queue_capacityMask, "f"), "f"); | ||
if (head < 2 && __classPrivateFieldGet(this, _Queue_tail, "f") > 10000 && __classPrivateFieldGet(this, _Queue_tail, "f") <= __classPrivateFieldGet(this, _Queue_list, "f").length >>> 2) | ||
const head = __classPrivateFieldGet(this, _head); | ||
const item = __classPrivateFieldGet(this, _list)[head]; | ||
__classPrivateFieldGet(this, _list)[head] = undefined; | ||
__classPrivateFieldSet(this, _head, (head + 1) & __classPrivateFieldGet(this, _capacityMask)); | ||
if (head < 2 && __classPrivateFieldGet(this, _tail) > 10000 && __classPrivateFieldGet(this, _tail) <= __classPrivateFieldGet(this, _list).length >>> 2) | ||
this.shrinkArray(); | ||
@@ -83,10 +85,10 @@ return item; | ||
push(item) { | ||
const tail = __classPrivateFieldGet(this, _Queue_tail, "f"); | ||
__classPrivateFieldGet(this, _Queue_list, "f")[tail] = item; | ||
__classPrivateFieldSet(this, _Queue_tail, (tail + 1) & __classPrivateFieldGet(this, _Queue_capacityMask, "f"), "f"); | ||
const tail = __classPrivateFieldGet(this, _tail); | ||
__classPrivateFieldGet(this, _list)[tail] = item; | ||
__classPrivateFieldSet(this, _tail, (tail + 1) & __classPrivateFieldGet(this, _capacityMask)); | ||
if (this.empty) | ||
this.growArray(); | ||
if (__classPrivateFieldGet(this, _Queue_head, "f") < __classPrivateFieldGet(this, _Queue_tail, "f")) | ||
return __classPrivateFieldGet(this, _Queue_tail, "f") - __classPrivateFieldGet(this, _Queue_head, "f"); | ||
return __classPrivateFieldGet(this, _Queue_capacityMask, "f") + 1 - (__classPrivateFieldGet(this, _Queue_head, "f") - __classPrivateFieldGet(this, _Queue_tail, "f")); | ||
if (__classPrivateFieldGet(this, _head) < __classPrivateFieldGet(this, _tail)) | ||
return __classPrivateFieldGet(this, _tail) - __classPrivateFieldGet(this, _head); | ||
return __classPrivateFieldGet(this, _capacityMask) + 1 - (__classPrivateFieldGet(this, _head) - __classPrivateFieldGet(this, _tail)); | ||
} | ||
@@ -97,8 +99,8 @@ /** Removes and returns the last element. */ | ||
return; | ||
const tail = __classPrivateFieldGet(this, _Queue_tail, "f"); | ||
const len = __classPrivateFieldGet(this, _Queue_list, "f").length; | ||
__classPrivateFieldSet(this, _Queue_tail, (tail - 1 + len) & __classPrivateFieldGet(this, _Queue_capacityMask, "f"), "f"); | ||
const item = __classPrivateFieldGet(this, _Queue_list, "f")[__classPrivateFieldGet(this, _Queue_tail, "f")]; | ||
__classPrivateFieldGet(this, _Queue_list, "f")[__classPrivateFieldGet(this, _Queue_tail, "f")] = undefined; | ||
if (__classPrivateFieldGet(this, _Queue_head, "f") < 2 && tail > 10000 && tail <= len >>> 2) | ||
const tail = __classPrivateFieldGet(this, _tail); | ||
const len = __classPrivateFieldGet(this, _list).length; | ||
__classPrivateFieldSet(this, _tail, (tail - 1 + len) & __classPrivateFieldGet(this, _capacityMask)); | ||
const item = __classPrivateFieldGet(this, _list)[__classPrivateFieldGet(this, _tail)]; | ||
__classPrivateFieldGet(this, _list)[__classPrivateFieldGet(this, _tail)] = undefined; | ||
if (__classPrivateFieldGet(this, _head) < 2 && tail > 10000 && tail <= len >>> 2) | ||
this.shrinkArray(); | ||
@@ -116,12 +118,12 @@ return item; | ||
index += len; | ||
index = (__classPrivateFieldGet(this, _Queue_head, "f") + index) & __classPrivateFieldGet(this, _Queue_capacityMask, "f"); | ||
return __classPrivateFieldGet(this, _Queue_list, "f")[index]; | ||
index = (__classPrivateFieldGet(this, _head) + index) & __classPrivateFieldGet(this, _capacityMask); | ||
return __classPrivateFieldGet(this, _list)[index]; | ||
} | ||
*[(_Queue_head = new WeakMap(), _Queue_tail = new WeakMap(), _Queue_capacityMask = new WeakMap(), _Queue_list = new WeakMap(), Symbol.iterator)]() { | ||
const head = __classPrivateFieldGet(this, _Queue_head, "f"); | ||
const tail = __classPrivateFieldGet(this, _Queue_tail, "f"); | ||
*[(_head = new WeakMap(), _tail = new WeakMap(), _capacityMask = new WeakMap(), _list = new WeakMap(), Symbol.iterator)]() { | ||
const head = __classPrivateFieldGet(this, _head); | ||
const tail = __classPrivateFieldGet(this, _tail); | ||
// Simply yield elements from left to right | ||
if (head <= tail) { | ||
for (let i = head; i < tail; ++i) | ||
yield __classPrivateFieldGet(this, _Queue_list, "f")[i]; | ||
yield __classPrivateFieldGet(this, _list)[i]; | ||
return; | ||
@@ -132,30 +134,30 @@ } | ||
for (let i = head; i < capacity; ++i) | ||
yield __classPrivateFieldGet(this, _Queue_list, "f")[i]; | ||
yield __classPrivateFieldGet(this, _list)[i]; | ||
// Then, wrap around and yield elements from start to tail | ||
for (let i = 0; i < tail; ++i) | ||
yield __classPrivateFieldGet(this, _Queue_list, "f")[i]; | ||
yield __classPrivateFieldGet(this, _list)[i]; | ||
} | ||
shrinkArray() { | ||
__classPrivateFieldGet(this, _Queue_list, "f").length >>>= 1; | ||
__classPrivateFieldSet(this, _Queue_capacityMask, __classPrivateFieldGet(this, _Queue_capacityMask, "f") >>> 1, "f"); | ||
__classPrivateFieldGet(this, _list).length >>>= 1; | ||
__classPrivateFieldSet(this, _capacityMask, __classPrivateFieldGet(this, _capacityMask) >>> 1); | ||
} | ||
growArray() { | ||
// Perform rotate-left if necessary | ||
if (__classPrivateFieldGet(this, _Queue_head, "f") > 0) { | ||
if (__classPrivateFieldGet(this, _head) > 0) { | ||
// Copy existing data from head to end | ||
const deleted = __classPrivateFieldGet(this, _Queue_list, "f").splice(__classPrivateFieldGet(this, _Queue_head, "f")); | ||
const deleted = __classPrivateFieldGet(this, _list).splice(__classPrivateFieldGet(this, _head)); | ||
// Then, plop all preceding elements after `deleted` | ||
deleted.push(...__classPrivateFieldGet(this, _Queue_list, "f")); | ||
deleted.push(...__classPrivateFieldGet(this, _list)); | ||
// Shift pointers accordingly | ||
__classPrivateFieldSet(this, _Queue_tail, __classPrivateFieldGet(this, _Queue_tail, "f") - __classPrivateFieldGet(this, _Queue_head, "f"), "f"); | ||
__classPrivateFieldSet(this, _Queue_head, 0, "f"); | ||
__classPrivateFieldSet(this, _tail, __classPrivateFieldGet(this, _tail) - __classPrivateFieldGet(this, _head)); | ||
__classPrivateFieldSet(this, _head, 0); | ||
// Discard old array | ||
__classPrivateFieldSet(this, _Queue_list, deleted, "f"); | ||
__classPrivateFieldSet(this, _list, deleted); | ||
} | ||
// Head is at 0 and array is now full, | ||
// therefore safe to extend | ||
__classPrivateFieldSet(this, _Queue_tail, __classPrivateFieldGet(this, _Queue_list, "f").length, "f"); | ||
__classPrivateFieldSet(this, _tail, __classPrivateFieldGet(this, _list).length); | ||
// Double the capacity | ||
__classPrivateFieldGet(this, _Queue_list, "f").length *= 2; | ||
__classPrivateFieldSet(this, _Queue_capacityMask, (__classPrivateFieldGet(this, _Queue_capacityMask, "f") << 1) | 1, "f"); | ||
__classPrivateFieldGet(this, _list).length *= 2; | ||
__classPrivateFieldSet(this, _capacityMask, (__classPrivateFieldGet(this, _capacityMask) << 1) | 1); | ||
} | ||
@@ -162,0 +164,0 @@ } |
@@ -36,5 +36,5 @@ import { DatabaseError } from './protocol'; | ||
iterator: ResultIterator<T>; | ||
dataHandler: DataHandler<T[] | null>; | ||
dataHandler: Callback<string | Error | DatabaseError | T[] | null>; | ||
nameHandler: (names: string[]) => void; | ||
}; | ||
export {}; |
@@ -11,3 +11,3 @@ "use strict"; | ||
return data | ||
.reduce((hash, d) => hash.update(d), (0, crypto_1.createHash)('md5')) | ||
.reduce((hash, d) => hash.update(d), crypto_1.createHash('md5')) | ||
.digest('hex'); | ||
@@ -14,0 +14,0 @@ } |
{ | ||
"name": "ts-postgres", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"description": "PostgreSQL client in TypeScript", | ||
@@ -46,3 +46,5 @@ "declaration": true, | ||
"/test/", | ||
"/src/logging.ts" | ||
"/src/logging.ts", | ||
"/src/queue.ts", | ||
"/src/utils.ts" | ||
], | ||
@@ -49,0 +51,0 @@ "coverageThreshold": { |
# ts-postgres | ||
[](http://travis-ci.org/malthe/ts-postgres) | ||
 | ||
<span class="badge-npmversion"><a href="https://npmjs.org/package/ts-postgres" title="View this project on NPM"><img src="https://img.shields.io/npm/v/ts-postgres.svg" alt="NPM version" /></a></span> | ||
@@ -5,0 +5,0 @@ <span class="badge-npmdownloads"><a href="https://npmjs.org/package/ts-postgres" title="View this project on NPM"><img src="https://img.shields.io/npm/dm/ts-postgres.svg" alt="NPM downloads" /></a></span> |
@@ -0,1 +1,2 @@ | ||
import { randomBytes } from 'crypto'; | ||
import { constants } from 'os'; | ||
@@ -190,6 +191,9 @@ import { Socket } from 'net'; | ||
private readonly clientNonce = randomBytes(18).toString('base64'); | ||
private serverSignature: string | null = null; | ||
private expect = 5; | ||
private stream = new Socket(); | ||
private offset = 0; | ||
private mustDrain = false; | ||
private activeRow: Array<Value> | null = null; | ||
@@ -233,2 +237,3 @@ private bindQueue = new Queue<RowDataHandlerInfo | null>(); | ||
/* istanbul ignore next */ | ||
this.stream.on('error', (error: NodeJS.ErrnoException) => { | ||
@@ -352,2 +357,3 @@ if (this.connecting) { | ||
let buffer: Buffer | null = null; | ||
let offset = 0; | ||
let remaining = 0; | ||
@@ -360,8 +366,8 @@ | ||
if (buffer && remaining) { | ||
const free = buffer.length - this.offset - remaining; | ||
let tail = this.offset + remaining; | ||
const free = buffer.length - offset - remaining; | ||
let tail = offset + remaining; | ||
if (free < length) { | ||
const tempBuf = Buffer.allocUnsafe(size); | ||
buffer.copy(tempBuf, 0, this.offset, tail); | ||
this.offset = 0; | ||
buffer.copy(tempBuf, 0, offset, tail); | ||
offset = 0; | ||
buffer = tempBuf; | ||
@@ -373,10 +379,13 @@ tail = remaining; | ||
buffer = newBuffer; | ||
this.offset = 0; | ||
offset = 0; | ||
} | ||
try { | ||
const read = this.receive(buffer, this.offset, size); | ||
this.offset += read; | ||
const read = this.receive(buffer, offset, size); | ||
offset += read; | ||
remaining = size - read; | ||
} catch (error) { | ||
if (this.connecting) { | ||
this.events.connect.emit(error as Error); | ||
} | ||
logger.warn(error); | ||
@@ -408,2 +417,4 @@ this.stream.destroy(); | ||
if (!error) return; | ||
this.connecting = false; | ||
this.stream.destroy(); | ||
throw error; | ||
@@ -695,4 +706,12 @@ }); | ||
const stack = new Error().stack; | ||
this.errorHandlerQueue.push( | ||
(error) => result.dataHandler(error) | ||
(error) => { | ||
if (stack !== undefined) | ||
error.stack = stack.replace( | ||
/(?<=^Error: )\n/, | ||
error.toString() + "\n" | ||
); | ||
result.dataHandler(error) | ||
} | ||
); | ||
@@ -795,6 +814,5 @@ | ||
let row: Array<Value> | null = null; | ||
let row = this.activeRow; | ||
const hasStreams = Object.keys(streams).length > 0; | ||
const mappedStreams = hasStreams ? names.map( | ||
@@ -814,2 +832,3 @@ name => streams[name] || null | ||
this.expect = 7; | ||
this.activeRow = row; | ||
return read; | ||
@@ -843,3 +862,5 @@ } | ||
buffer.writeInt16BE(row.length, offset - 2); | ||
return offset - 7; | ||
this.expect = 12; | ||
this.activeRow = row; | ||
return read + end; | ||
} | ||
@@ -853,2 +874,3 @@ | ||
if (size < frame + 5) { | ||
this.activeRow = row; | ||
this.expect = 5; | ||
@@ -860,2 +882,4 @@ return read; | ||
} | ||
this.activeRow = null; | ||
} | ||
@@ -881,2 +905,4 @@ | ||
const code = buffer.readInt32BE(start); | ||
outer: | ||
/* istanbul ignore next */ | ||
switch (code) { | ||
@@ -904,2 +930,28 @@ case 0: { | ||
} | ||
case 10: { | ||
const reader = new Reader(buffer, start + 4); | ||
const mechanisms = []; | ||
while (true) { | ||
const mechanism = reader.readCString(this.encoding); | ||
if (mechanism.length === 0) break; | ||
if (writer.saslInitialResponse(mechanism, this.clientNonce)) | ||
break outer; | ||
mechanisms.push(mechanism); | ||
} | ||
throw new Error( | ||
`SASL authentication unsupported (mechanisms: ${mechanisms.join(', ')})` | ||
); | ||
} | ||
case 11: { | ||
const data = buffer.slice(start + 4, start + length).toString("utf8"); | ||
const password = this.config.password || defaults.password || ''; | ||
this.serverSignature = writer.saslResponse(data, password, this.clientNonce); | ||
break; | ||
} | ||
case 12: { | ||
const data = buffer.slice(start + 4, start + length).toString("utf8"); | ||
if (!this.serverSignature) throw new Error('Server signature missing'); | ||
writer.saslFinal(data, this.serverSignature); | ||
break; | ||
} | ||
default: | ||
@@ -930,7 +982,7 @@ throw new Error( | ||
if (preflight.dataHandler) { | ||
const info = { | ||
handler: preflight.dataHandler, | ||
description: null, | ||
}; | ||
if (preflight.bind) { | ||
const info = { | ||
handler: preflight.dataHandler, | ||
description: null, | ||
}; | ||
this.bindAndExecute( | ||
@@ -942,3 +994,3 @@ info, | ||
} else { | ||
preflight.dataHandler.callback(null); | ||
this.activeDataHandlerInfo = info; | ||
} | ||
@@ -952,3 +1004,2 @@ } else { | ||
case Message.CommandComplete: { | ||
// This is unset if the query had no row data. | ||
const info = this.activeDataHandlerInfo; | ||
@@ -960,3 +1011,3 @@ if (info) { | ||
info.handler.callback(status); | ||
info.handler.callback(status || null); | ||
this.activeDataHandlerInfo = null; | ||
@@ -978,4 +1029,5 @@ } | ||
if (this.connecting) throw error; | ||
this.events.error.emit(error); | ||
loop: | ||
@@ -1013,3 +1065,2 @@ while (true) { | ||
} | ||
break; | ||
@@ -1016,0 +1067,0 @@ } |
@@ -5,2 +5,3 @@ import { Socket } from 'net'; | ||
import { postgresqlErrorCodes } from './errors'; | ||
import { hi, hmacSha256, sha256, xorBuffers } from './sasl'; | ||
import { sum } from './utils'; | ||
@@ -34,3 +35,4 @@ import { | ||
Query = 0x51, | ||
Sync = 0x53 | ||
Sync = 0x53, | ||
SASLResponse = 0x70 | ||
} | ||
@@ -609,3 +611,2 @@ | ||
private outgoing: ElasticBuffer = new ElasticBuffer(4096); | ||
//private outgoing_msg_debug: Array<number | null> = []; | ||
@@ -1003,2 +1004,72 @@ constructor(private readonly encoding: BufferEncoding) { } | ||
saslInitialResponse(mechanism: string, clientNonce: string) { | ||
if (mechanism !== 'SCRAM-SHA-256') return false; | ||
const response = Buffer.from('n,,n=*,r=' + clientNonce); | ||
this.enqueue( | ||
Command.SASLResponse, [ | ||
makeBufferSegment(mechanism, this.encoding, true), | ||
[SegmentType.Int32BE, response.length], | ||
[SegmentType.Buffer, response] | ||
]); | ||
return true; | ||
} | ||
saslResponse(data: string, password: string, clientNonce: string) { | ||
const m = Object.fromEntries(data.split(',').map( | ||
(attr) => [attr[0], attr.substring(2)]) | ||
); | ||
if (!(m.i && m.r && m.s)) throw new Error("SASL message parse error"); | ||
const nonce = m.r; | ||
if (!nonce.startsWith(clientNonce)) | ||
throw new Error("SASL nonce mismatch"); | ||
if (nonce.length === clientNonce.length) | ||
throw new Error("SASL nonce too short"); | ||
const iterations = parseInt(m.i, 10); | ||
const salt = Buffer.from(m.s, 'base64'); | ||
const saltedPassword = hi(password, salt, iterations) | ||
const clientKey = hmacSha256(saltedPassword, 'Client Key'); | ||
const storedKey = sha256(clientKey); | ||
const clientFinalMessageWithoutProof = 'c=biws,r=' + nonce; | ||
const clientFirstMessageBare = 'n=*,r=' + clientNonce; | ||
const serverFirstMessage = data; | ||
const authMessage = ( | ||
clientFirstMessageBare + ',' + | ||
serverFirstMessage + ',' + | ||
clientFinalMessageWithoutProof | ||
); | ||
const clientSignature = hmacSha256(storedKey, authMessage); | ||
const clientProofBytes = xorBuffers(clientKey, clientSignature); | ||
const clientProof = clientProofBytes.toString('base64'); | ||
const serverKey = hmacSha256(saltedPassword, 'Server Key'); | ||
const serverSignatureBytes = hmacSha256(serverKey, authMessage); | ||
const response = clientFinalMessageWithoutProof + ',p=' + clientProof; | ||
const serverSignature = serverSignatureBytes.toString('base64'); | ||
this.enqueue( | ||
Command.SASLResponse, [ | ||
makeBufferSegment(response, this.encoding, false) | ||
]); | ||
return serverSignature; | ||
} | ||
saslFinal(data: string, serverSignature: string) { | ||
if (!data.split(',').find((attr) => { | ||
if (attr[0] === 'v') { | ||
return (attr.substr(2) === serverSignature); | ||
} | ||
return false; | ||
})) throw new Error('SASL server signature does not match'); | ||
} | ||
send(socket: Socket) { | ||
@@ -1005,0 +1076,0 @@ if (this.outgoing.empty) return false; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
318205
5.03%55
7.84%6024
4.87%