diff --git a/src/metadata-parser.ts b/src/metadata-parser.ts index 5108fd1f3..5e5e02d29 100644 --- a/src/metadata-parser.ts +++ b/src/metadata-parser.ts @@ -53,268 +53,205 @@ export type Metadata = { cryptoMetadata?: CryptoMetadata; } & BaseMetadata; +class UnknownTypeError extends Error { } +class NotEnoughDataError extends Error { } -function readCollation(parser: Parser, callback: (collation: Collation) => void) { +function checkDataLength(buffer: Buffer, offset: number, numBytes: number): void { + if (buffer.length < offset + numBytes) { + throw new NotEnoughDataError(); + } +} + +function readFromBuffer(parser: Parser, length: number): Buffer { + checkDataLength(parser.buffer, parser.position, length); + const result = parser.buffer.slice(parser.position, parser.position + length); + parser.position += length; + return result; +} + +function readUInt8(parser: Parser): number { + checkDataLength(parser.buffer, parser.position, 1); + const data = parser.buffer.readUInt8(parser.position); + parser.position += 1; + return data; +} + +function readUInt16LE(parser: Parser): number { + checkDataLength(parser.buffer, parser.position, 2); + const data = parser.buffer.readUInt16LE(parser.position); + parser.position += 2; + return data; +} + +function readUInt32LE(parser: Parser): number { + checkDataLength(parser.buffer, parser.position, 4); + const data = parser.buffer.readUInt32LE(parser.position); + parser.position += 4; + return data; +} + +function readBVarChar(parser: Parser): string { + const length = readUInt8(parser) * 2; + const data = readFromBuffer(parser, length).toString('ucs2'); + return data; +} + +function readUsVarChar(parser: Parser): string { + const length = readUInt16LE(parser) * 2; + const data = readFromBuffer(parser, length).toString('ucs2'); + return data; +} + +function readCollation(parser: Parser): Collation { // s2.2.5.1.2 - parser.readBuffer(5, (collationData) => { - callback(Collation.fromBuffer(collationData)); - }); + const collationData = readFromBuffer(parser, 5); + return Collation.fromBuffer(collationData); } -function readSchema(parser: Parser, callback: (schema: XmlSchema | undefined) => void) { - // s2.2.5.5.3 - parser.readUInt8((schemaPresent) => { - if (schemaPresent === 0x01) { - parser.readBVarChar((dbname) => { - parser.readBVarChar((owningSchema) => { - parser.readUsVarChar((xmlSchemaCollection) => { - callback({ - dbname: dbname, - owningSchema: owningSchema, - xmlSchemaCollection: xmlSchemaCollection - }); - }); - }); - }); - } else { - callback(undefined); - } - }); +function readSchema(parser: Parser): XmlSchema | undefined { + const schemaPresent = readUInt8(parser); + if (schemaPresent === 0x01) { + const dbname = readBVarChar(parser); + const owningSchema = readBVarChar(parser); + const xmlSchemaCollection = readUsVarChar(parser); + return { + dbname: dbname, + owningSchema: owningSchema, + xmlSchemaCollection: xmlSchemaCollection + }; + } else { + return undefined; + } } -function readUDTInfo(parser: Parser, callback: (udtInfo: UdtInfo | undefined) => void) { - parser.readUInt16LE((maxByteSize) => { - parser.readBVarChar((dbname) => { - parser.readBVarChar((owningSchema) => { - parser.readBVarChar((typeName) => { - parser.readUsVarChar((assemblyName) => { - callback({ - maxByteSize: maxByteSize, - dbname: dbname, - owningSchema: owningSchema, - typeName: typeName, - assemblyName: assemblyName - }); - }); - }); - }); - }); - }); +function readUDTInfo(parser: Parser) { + const maxByteSize = readUInt16LE(parser); + const dbname = readBVarChar(parser); + const owningSchema = readBVarChar(parser); + const typeName = readBVarChar(parser); + const assemblyName = readUsVarChar(parser); + return { + maxByteSize: maxByteSize, + dbname: dbname, + owningSchema: owningSchema, + typeName: typeName, + assemblyName: assemblyName + }; } -function metadataParse(parser: Parser, options: ParserOptions, callback: (metadata: Metadata) => void) { - (options.tdsVersion < '7_2' ? parser.readUInt16LE : parser.readUInt32LE).call(parser, (userType) => { - parser.readUInt16LE((flags) => { - parser.readUInt8((typeNumber) => { - const type: DataType = TYPE[typeNumber]; - - if (!type) { - throw new Error(sprintf('Unrecognised data type 0x%02X', typeNumber)); - } - - switch (type.name) { - case 'Null': - case 'TinyInt': - case 'SmallInt': - case 'Int': - case 'BigInt': - case 'Real': - case 'Float': - case 'SmallMoney': - case 'Money': - case 'Bit': - case 'SmallDateTime': - case 'DateTime': - case 'Date': - return callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: undefined, - schema: undefined, - udtInfo: undefined - }); - - case 'IntN': - case 'FloatN': - case 'MoneyN': - case 'BitN': - case 'UniqueIdentifier': - case 'DateTimeN': - return parser.readUInt8((dataLength) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - - case 'Variant': - return parser.readUInt32LE((dataLength) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - - case 'VarChar': - case 'Char': - case 'NVarChar': - case 'NChar': - return parser.readUInt16LE((dataLength) => { - readCollation(parser, (collation) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: collation, - precision: undefined, - scale: undefined, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - }); - - case 'Text': - case 'NText': - return parser.readUInt32LE((dataLength) => { - readCollation(parser, (collation) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: collation, - precision: undefined, - scale: undefined, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - }); - - case 'VarBinary': - case 'Binary': - return parser.readUInt16LE((dataLength) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - - case 'Image': - return parser.readUInt32LE((dataLength) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - - case 'Xml': - return readSchema(parser, (schema) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: undefined, - schema: schema, - udtInfo: undefined - }); - }); - - case 'Time': - case 'DateTime2': - case 'DateTimeOffset': - return parser.readUInt8((scale) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: scale, - dataLength: undefined, - schema: undefined, - udtInfo: undefined - }); - }); - - case 'NumericN': - case 'DecimalN': - return parser.readUInt8((dataLength) => { - parser.readUInt8((precision) => { - parser.readUInt8((scale) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: precision, - scale: scale, - dataLength: dataLength, - schema: undefined, - udtInfo: undefined - }); - }); - }); - }); - - case 'UDT': - return readUDTInfo(parser, (udtInfo) => { - callback({ - userType: userType, - flags: flags, - type: type, - collation: undefined, - precision: undefined, - scale: undefined, - dataLength: undefined, - schema: undefined, - udtInfo: udtInfo - }); - }); - - default: - throw new Error(sprintf('Unrecognised type %s', type.name)); - } - }); - }); - }); +function metadataParse(parser: Parser, options: ParserOptions): Metadata { + let userType: number; + + if (options.tdsVersion < '7_2') { + userType = readUInt16LE(parser); + } else { + userType = readUInt32LE(parser); + } + + const flags = readUInt16LE(parser); + + const typeNumber = readUInt8(parser); + const type: DataType = TYPE[typeNumber]; + + let collation: Collation | undefined; + let precision: number | undefined; + let scale: number | undefined; + let dataLength: number | undefined; + let schema: XmlSchema | undefined; + let udtInfo: UdtInfo | undefined; + + if (!type) { + throw new UnknownTypeError(sprintf('Unrecognised data type 0x%02X', typeNumber)); + } + + switch (type.name) { + case 'Null': + case 'TinyInt': + case 'SmallInt': + case 'Int': + case 'BigInt': + case 'Real': + case 'Float': + case 'SmallMoney': + case 'Money': + case 'Bit': + case 'SmallDateTime': + case 'DateTime': + case 'Date': + break; + + case 'IntN': + case 'FloatN': + case 'MoneyN': + case 'BitN': + case 'UniqueIdentifier': + case 'DateTimeN': + dataLength = readUInt8(parser); + break; + + case 'Variant': + dataLength = readUInt32LE(parser); + break; + + case 'VarChar': + case 'Char': + case 'NVarChar': + case 'NChar': + dataLength = readUInt16LE(parser); + collation = readCollation(parser); + break; + + case 'Text': + case 'NText': + dataLength = readUInt32LE(parser); + collation = readCollation(parser); + break; + + case 'VarBinary': + case 'Binary': + dataLength = readUInt16LE(parser); + break; + + case 'Image': + dataLength = readUInt32LE(parser); + break; + + case 'Xml': + schema = readSchema(parser); + break; + + case 'Time': + case 'DateTime2': + case 'DateTimeOffset': + scale = readUInt8(parser); + break; + + case 'NumericN': + case 'DecimalN': + dataLength = readUInt8(parser); + precision = readUInt8(parser); + scale = readUInt8(parser); + break; + + case 'UDT': + udtInfo = readUDTInfo(parser); + break; + + default: + throw new UnknownTypeError(sprintf('Unrecognised type %s', type.name)); + } + + return { + userType: userType, + flags: flags, + type: type, + collation: collation, + precision: precision, + scale: scale, + dataLength: dataLength, + schema: schema, + udtInfo: udtInfo + }; } export default metadataParse; diff --git a/src/token/colmetadata-token-parser.ts b/src/token/colmetadata-token-parser.ts index cab333c5a..23be005d1 100644 --- a/src/token/colmetadata-token-parser.ts +++ b/src/token/colmetadata-token-parser.ts @@ -12,6 +12,8 @@ export interface ColumnMetadata extends Metadata { tableName?: string | string[] | undefined; } +class NotEnoughDataError extends Error { } + function readTableName(parser: Parser, options: ParserOptions, metadata: Metadata, callback: (tableName?: string | string[]) => void) { if (metadata.type.hasTableName) { if (options.tdsVersion >= '7_2') { @@ -60,22 +62,32 @@ function readColumnName(parser: Parser, options: ParserOptions, index: number, m } function readColumn(parser: Parser, options: ParserOptions, index: number, callback: (column: ColumnMetadata) => void) { - metadataParse(parser, options, (metadata) => { - readTableName(parser, options, metadata, (tableName) => { - readColumnName(parser, options, index, metadata, (colName) => { - callback({ - userType: metadata.userType, - flags: metadata.flags, - type: metadata.type, - collation: metadata.collation, - precision: metadata.precision, - scale: metadata.scale, - udtInfo: metadata.udtInfo, - dataLength: metadata.dataLength, - schema: metadata.schema, - colName: colName, - tableName: tableName - }); + let metadata!: Metadata; + const offset = parser.position; + try { + metadata = metadataParse(parser, options); + } catch (err) { + if (err instanceof NotEnoughDataError) { + return parser.suspend(() => { + parser.position = offset; + readColumn(parser, options, index, callback); + }); + } + } + readTableName(parser, options, metadata, (tableName) => { + readColumnName(parser, options, index, metadata, (colName) => { + callback({ + userType: metadata.userType, + flags: metadata.flags, + type: metadata.type, + collation: metadata.collation, + precision: metadata.precision, + scale: metadata.scale, + udtInfo: metadata.udtInfo, + dataLength: metadata.dataLength, + schema: metadata.schema, + colName: colName, + tableName: tableName }); }); }); diff --git a/src/token/returnvalue-token-parser.ts b/src/token/returnvalue-token-parser.ts index 42699497a..baadba6f4 100644 --- a/src/token/returnvalue-token-parser.ts +++ b/src/token/returnvalue-token-parser.ts @@ -4,32 +4,46 @@ import Parser, { ParserOptions } from './stream-parser'; import { ReturnValueToken } from './token'; -import metadataParse from '../metadata-parser'; +import metadataParse, { Metadata } from '../metadata-parser'; import valueParse from '../value-parser'; +class NotEnoughDataError extends Error { } + function returnParser(parser: Parser, options: ParserOptions, callback: (token: ReturnValueToken) => void) { parser.readUInt16LE((paramOrdinal) => { parser.readBVarChar((paramName) => { if (paramName.charAt(0) === '@') { paramName = paramName.slice(1); } - + parser.position += 1; // status - parser.readUInt8(() => { - metadataParse(parser, options, (metadata) => { - valueParse(parser, metadata, options, (value) => { - callback(new ReturnValueToken({ - paramOrdinal: paramOrdinal, - paramName: paramName, - metadata: metadata, - value: value - })); - }); - }); - }); + readValue(parser, options, paramOrdinal, paramName, parser.position, callback); + }); }); } +function readValue(parser: Parser, options: ParserOptions, paramOrdinal: number, paramName: string, originalPosition: number, callback: (token: ReturnValueToken) => void) { + let metadata!: Metadata; + parser.position = originalPosition; + try { + metadata = metadataParse(parser, options); + } catch (err) { + if (err instanceof NotEnoughDataError) { + return parser.suspend(() => { + readValue(parser, options, paramOrdinal, paramName, originalPosition, callback); + }); + } + } + valueParse(parser, metadata, options, (value) => { + callback(new ReturnValueToken({ + paramOrdinal: paramOrdinal, + paramName: paramName, + metadata: metadata, + value: value + })); + }); +} + export default returnParser; module.exports = returnParser; diff --git a/src/value-parser.ts b/src/value-parser.ts index 2c1f303c6..21669153c 100644 --- a/src/value-parser.ts +++ b/src/value-parser.ts @@ -458,17 +458,15 @@ function readVariant(parser: Parser, options: ParserOptions, dataLength: number, case 'VarChar': case 'Char': return parser.readUInt16LE((_maxLength) => { - readCollation(parser, (collation) => { - readChars(parser, dataLength, collation.codepage!, callback); - }); + const collation = readCollation(parser); + readChars(parser, dataLength, collation.codepage!, callback); }); case 'NVarChar': case 'NChar': return parser.readUInt16LE((_maxLength) => { - readCollation(parser, (_collation) => { - readNChars(parser, dataLength, callback); - }); + readCollation(parser); + readNChars(parser, dataLength, callback); }); default: