380 lines
34 KiB
JavaScript
380 lines
34 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
Object.defineProperty(exports, "__esModule", {
|
||
|
|
value: true
|
||
|
|
});
|
||
|
|
exports.default = void 0;
|
||
|
|
exports.readCollation = readCollation;
|
||
|
|
exports.readMetadata = readMetadata;
|
||
|
|
var _collation = require("./collation");
|
||
|
|
var _dataType = require("./data-type");
|
||
|
|
var _sprintfJs = require("sprintf-js");
|
||
|
|
var _helpers = require("./token/helpers");
|
||
|
|
function readCollation(buf, offset) {
|
||
|
|
offset = +offset;
|
||
|
|
if (buf.length < offset + 5) {
|
||
|
|
throw new _helpers.NotEnoughDataError(offset + 5);
|
||
|
|
}
|
||
|
|
const collation = _collation.Collation.fromBuffer(buf.slice(offset, offset + 5));
|
||
|
|
return new _helpers.Result(collation, offset + 5);
|
||
|
|
}
|
||
|
|
function readSchema(buf, offset) {
|
||
|
|
offset = +offset;
|
||
|
|
let schemaPresent;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: schemaPresent
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
if (schemaPresent !== 0x01) {
|
||
|
|
return new _helpers.Result(undefined, offset);
|
||
|
|
}
|
||
|
|
let dbname;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dbname
|
||
|
|
} = (0, _helpers.readBVarChar)(buf, offset));
|
||
|
|
let owningSchema;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: owningSchema
|
||
|
|
} = (0, _helpers.readBVarChar)(buf, offset));
|
||
|
|
let xmlSchemaCollection;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: xmlSchemaCollection
|
||
|
|
} = (0, _helpers.readUsVarChar)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
dbname,
|
||
|
|
owningSchema,
|
||
|
|
xmlSchemaCollection
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
function readUDTInfo(buf, offset) {
|
||
|
|
let maxByteSize;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: maxByteSize
|
||
|
|
} = (0, _helpers.readUInt16LE)(buf, offset));
|
||
|
|
let dbname;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dbname
|
||
|
|
} = (0, _helpers.readBVarChar)(buf, offset));
|
||
|
|
let owningSchema;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: owningSchema
|
||
|
|
} = (0, _helpers.readBVarChar)(buf, offset));
|
||
|
|
let typeName;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: typeName
|
||
|
|
} = (0, _helpers.readBVarChar)(buf, offset));
|
||
|
|
let assemblyName;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: assemblyName
|
||
|
|
} = (0, _helpers.readUsVarChar)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
maxByteSize: maxByteSize,
|
||
|
|
dbname: dbname,
|
||
|
|
owningSchema: owningSchema,
|
||
|
|
typeName: typeName,
|
||
|
|
assemblyName: assemblyName
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
function readMetadata(buf, offset, options) {
|
||
|
|
let userType;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: userType
|
||
|
|
} = (options.tdsVersion < '7_2' ? _helpers.readUInt16LE : _helpers.readUInt32LE)(buf, offset));
|
||
|
|
let flags;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: flags
|
||
|
|
} = (0, _helpers.readUInt16LE)(buf, offset));
|
||
|
|
let typeNumber;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: typeNumber
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
const type = _dataType.TYPE[typeNumber];
|
||
|
|
if (!type) {
|
||
|
|
throw new Error((0, _sprintfJs.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 new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: undefined,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
case 'IntN':
|
||
|
|
case 'FloatN':
|
||
|
|
case 'MoneyN':
|
||
|
|
case 'BitN':
|
||
|
|
case 'UniqueIdentifier':
|
||
|
|
case 'DateTimeN':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'Variant':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt32LE)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'VarChar':
|
||
|
|
case 'Char':
|
||
|
|
case 'NVarChar':
|
||
|
|
case 'NChar':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt16LE)(buf, offset));
|
||
|
|
let collation;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: collation
|
||
|
|
} = readCollation(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: collation,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'Text':
|
||
|
|
case 'NText':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt32LE)(buf, offset));
|
||
|
|
let collation;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: collation
|
||
|
|
} = readCollation(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: collation,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'VarBinary':
|
||
|
|
case 'Binary':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt16LE)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'Image':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt32LE)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'Xml':
|
||
|
|
{
|
||
|
|
let schema;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: schema
|
||
|
|
} = readSchema(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: undefined,
|
||
|
|
schema: schema,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'Time':
|
||
|
|
case 'DateTime2':
|
||
|
|
case 'DateTimeOffset':
|
||
|
|
{
|
||
|
|
let scale;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: scale
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: scale,
|
||
|
|
dataLength: undefined,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'NumericN':
|
||
|
|
case 'DecimalN':
|
||
|
|
{
|
||
|
|
let dataLength;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: dataLength
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
let precision;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: precision
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
let scale;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: scale
|
||
|
|
} = (0, _helpers.readUInt8)(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: precision,
|
||
|
|
scale: scale,
|
||
|
|
dataLength: dataLength,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: undefined
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
case 'UDT':
|
||
|
|
{
|
||
|
|
let udtInfo;
|
||
|
|
({
|
||
|
|
offset,
|
||
|
|
value: udtInfo
|
||
|
|
} = readUDTInfo(buf, offset));
|
||
|
|
return new _helpers.Result({
|
||
|
|
userType: userType,
|
||
|
|
flags: flags,
|
||
|
|
type: type,
|
||
|
|
collation: undefined,
|
||
|
|
precision: undefined,
|
||
|
|
scale: undefined,
|
||
|
|
dataLength: undefined,
|
||
|
|
schema: undefined,
|
||
|
|
udtInfo: udtInfo
|
||
|
|
}, offset);
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
throw new Error((0, _sprintfJs.sprintf)('Unrecognised type %s', type.name));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function metadataParse(parser, options, callback) {
|
||
|
|
(async () => {
|
||
|
|
while (true) {
|
||
|
|
let result;
|
||
|
|
try {
|
||
|
|
result = readMetadata(parser.buffer, parser.position, options);
|
||
|
|
} catch (err) {
|
||
|
|
if (err instanceof _helpers.NotEnoughDataError) {
|
||
|
|
await parser.waitForChunk();
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
throw err;
|
||
|
|
}
|
||
|
|
parser.position = result.offset;
|
||
|
|
return callback(result.value);
|
||
|
|
}
|
||
|
|
})();
|
||
|
|
}
|
||
|
|
var _default = exports.default = metadataParse;
|
||
|
|
module.exports = metadataParse;
|
||
|
|
module.exports.readCollation = readCollation;
|
||
|
|
module.exports.readMetadata = readMetadata;
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfY29sbGF0aW9uIiwicmVxdWlyZSIsIl9kYXRhVHlwZSIsIl9zcHJpbnRmSnMiLCJfaGVscGVycyIsInJlYWRDb2xsYXRpb24iLCJidWYiLCJvZmZzZXQiLCJsZW5ndGgiLCJOb3RFbm91Z2hEYXRhRXJyb3IiLCJjb2xsYXRpb24iLCJDb2xsYXRpb24iLCJmcm9tQnVmZmVyIiwic2xpY2UiLCJSZXN1bHQiLCJyZWFkU2NoZW1hIiwic2NoZW1hUHJlc2VudCIsInZhbHVlIiwicmVhZFVJbnQ4IiwidW5kZWZpbmVkIiwiZGJuYW1lIiwicmVhZEJWYXJDaGFyIiwib3duaW5nU2NoZW1hIiwieG1sU2NoZW1hQ29sbGVjdGlvbiIsInJlYWRVc1ZhckNoYXIiLCJyZWFkVURUSW5mbyIsIm1heEJ5dGVTaXplIiwicmVhZFVJbnQxNkxFIiwidHlwZU5hbWUiLCJhc3NlbWJseU5hbWUiLCJyZWFkTWV0YWRhdGEiLCJvcHRpb25zIiwidXNlclR5cGUiLCJ0ZHNWZXJzaW9uIiwicmVhZFVJbnQzMkxFIiwiZmxhZ3MiLCJ0eXBlTnVtYmVyIiwidHlwZSIsIlRZUEUiLCJFcnJvciIsInNwcmludGYiLCJuYW1lIiwicHJlY2lzaW9uIiwic2NhbGUiLCJkYXRhTGVuZ3RoIiwic2NoZW1hIiwidWR0SW5mbyIsIm1ldGFkYXRhUGFyc2UiLCJwYXJzZXIiLCJjYWxsYmFjayIsInJlc3VsdCIsImJ1ZmZlciIsInBvc2l0aW9uIiwiZXJyIiwid2FpdEZvckNodW5rIiwiX2RlZmF1bHQiLCJleHBvcnRzIiwiZGVmYXVsdCIsIm1vZHVsZSJdLCJzb3VyY2VzIjpbIi4uL3NyYy9tZXRhZGF0YS1wYXJzZXIudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29sbGF0aW9uIH0gZnJvbSAnLi9jb2xsYXRpb24nO1xuaW1wb3J0IFBhcnNlciwgeyB0eXBlIFBhcnNlck9wdGlvbnMgfSBmcm9tICcuL3Rva2VuL3N0cmVhbS1wYXJzZXInO1xuaW1wb3J0IHsgVFlQRSwgdHlwZSBEYXRhVHlwZSB9IGZyb20gJy4vZGF0YS10eXBlJztcbmltcG9ydCB7IHR5cGUgQ3J5cHRvTWV0YWRhdGEgfSBmcm9tICcuL2Fsd2F5cy1lbmNyeXB0ZWQvdHlwZXMnO1xuXG5pbXBvcnQgeyBzcHJpbnRmIH0gZnJvbSAnc3ByaW50Zi1qcyc7XG5cbmltcG9ydCB7IFJlc3VsdCwgTm90RW5vdWdoRGF0YUVycm9yLCByZWFkVUludDgsIHJlYWRCVmFyQ2hhciwgcmVhZFVzVmFyQ2hhciwgcmVhZFVJbnQxNkxFLCByZWFkVUludDMyTEUgfSBmcm9tICcuL3Rva2VuL2hlbHBlcnMnO1xuXG5pbnRlcmZhY2UgWG1sU2NoZW1hIHtcbiAgZGJuYW1lOiBzdHJpbmc7XG4gIG93bmluZ1NjaGVtYTogc3RyaW5nO1xuICB4bWxTY2hlbWFDb2xsZWN0aW9uOiBzdHJpbmc7XG59XG5cbmludGVyZmFjZSBVZHRJbmZvIHtcbiAgbWF4Qnl0ZVNpemU6IG51bWJlcjtcbiAgZGJuYW1lOiBzdHJpbmc7XG4gIG93bmluZ1NjaGVtYTogc3RyaW5nO1xuICB0eXBlTmFtZTogc3RyaW5nO1xuICBhc3NlbWJseU5hbWU6IHN0cmluZztcbn1cblxuZXhwb3J0IHR5cGUgQmFzZU1ldGFkYXRhID0ge1xuICB1c2VyVHlwZTogbnVtYmVyO1xuXG4gIGZsYWdzOiBudW1iZXI7XG4gIC8qKlxuICAgKiBUaGUgY29sdW1uJ3MgdHlwZSwgc3VjaCBhcyBWYXJDaGFyLCBJbnQgb3IgQmluYXJ5LlxuICAgKi9cbiAgdHlwZTogRGF0YVR5cGU7XG5cbiAgY29sbGF0aW9uOiBDb2xsYXRpb24gfCB1bmRlZmluZWQ7XG4gIC8qKlxuICAgKiBUaGUgcHJlY2lzaW9uLiBPbmx5IGFwcGxpY2FibGUgdG8gbnVtZXJpYyBhbmQgZGVjaW1hbC5cbiAgICovXG4gIHByZWNpc2lvbjogbnVtYmVyIHwgdW5kZWZpbmVkO1xuXG4gIC8qKlxuICAgKiBUaGUgc2NhbGUuIE9ubHkgYXBwbGljYWJsZSB0byBudW1lcmljLCBkZWNpbWFsLCB0aW1lLCBkYXRldGltZTIgYW5kIGRhdGV0aW1lb2Zmc2V0LlxuICAgKi9cbiAgc2NhbGU6IG51bWJlciB8IHVuZGVmaW5lZDtcblxuICAvKipcbiAgICogVGhlIGxlbmd0aCwgZm9yIGNoYXIsIHZhcmNoYXIsIG52YXJjaGFyIGFuZCB2YXJiaW5hcnkuXG4gICAqL1xuICBkYXRhTGVuZ3RoOiBudW1iZXIgfCB1bmRlZmluZWQ7XG5cbiAgc2NoZW1hOiBYbWxTY2hlbWEgfCB1bmRlZmluZWQ7XG5cbiAgdWR0SW5mbzogVWR0SW5mbyB8IHVuZGVmaW5lZDtcbn1cblxuZXhwb3J0IHR5cGUgTWV0YWRhdGEgPSB7XG4gIGNyeXB0b01ldGFkYXRhPzogQ3J5cHRvTWV0YWRhdGE7XG59ICYgQmFzZU1ldGFkYXRhO1xuXG5mdW5jdGlvbiByZWFkQ29sbGF0aW9uKGJ1ZjogQnVmZmVyLCBvZmZzZXQ6IG51bWJlcik6IFJlc3VsdDxDb2xsYXRpb24+IHtcbiAgb2Zmc2V0ID0gK29mZnNldDtcblxuICBpZiAoYnVmLmxlbmd0aCA8IG9mZnNldCArIDUpIHtcbiAgICB0aHJvdyBuZXcgTm90RW5vdWdoRGF0YUVycm9yKG9mZnNldCArIDUpO1xuICB9XG5cbiAgY29uc3QgY29sbGF0aW9uID0gQ29sbGF0aW9uLmZyb21CdWZmZXIoYnVmLnNsaWNlKG9mZnNldCwgb2Zmc2V0ICsgNSkpO1xuICByZXR1cm4gbmV3IFJlc3VsdChjb2xsYXRpb24sIG9mZnNldCArIDUpO1xufVxuXG5mdW5jdGlvbiByZWFkU2NoZW1hKGJ1ZjogQnVmZmVyLCBvZmZzZXQ6IG51bWJlcik6IFJlc3VsdDxYbWxTY2hlbWEgfCB1bmRlZmluZWQ+IHtcbiAgb2Zmc2V0ID0gK29mZnNldDtcblxuICBsZXQgc2NoZW1hUHJlc2VudDtcbiAgKHsgb2Zmc2V0LCB2YWx1ZTogc2NoZW1hUHJlc2VudCB9ID0gcmVhZFVJbnQ4KGJ1Ziwgb2Zmc2V0KSk7XG5cbiAgaWYgKHNjaGVtYVByZXNlbnQgIT09IDB4MDEpIHtcbiAgICByZXR1cm4gbmV3IFJlc3VsdCh1bmRlZmluZWQsIG9mZnNldCk7XG4gIH1cblxuICBsZXQgZGJuYW1lO1xuICAoeyBvZmZzZXQsIHZhbHVlOiBkYm5hbWUgfSA9IHJlYWRCVmFyQ2hhcihidWYsIG9mZnNldCkpO1xuXG4gIGxldCBvd25pbmdTY2hlbWE7XG4gICh7IG9mZnNldCwgdmFsdWU6IG93bmluZ1NjaGVtYSB9ID0gcmVhZEJWYXJDaGFyKGJ1Ziwgb2Zmc2V0KSk7XG5cbiAgbGV0IHhtbFNjaGVtYUNvbGxlY3Rpb247XG4gICh7IG9mZnNldCwgdmFsdWU6IHhtbFNjaGVtYUNvbGxlY3Rpb24gfSA9IHJlYWRVc1ZhckNoYXIoYnVmLCBvZmZzZXQpKTtcblxuICByZXR1cm4gbmV3IFJlc3VsdCh7IGRibmFtZSwgb3duaW5nU2N
|