271 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| //  SPDX-License-Identifier: LGPL-2.1-or-later
 | |
| //  Copyright (c) 2015-2024 MariaDB Corporation Ab
 | |
| 
 | |
| 'use strict';
 | |
| const hexArray = '0123456789ABCDEF'.split('');
 | |
| const Errors = require('../misc/errors');
 | |
| const Iconv = require('iconv-lite');
 | |
| const TextEncoder = require('../cmd/encoder/text-encoder');
 | |
| 
 | |
| /**
 | |
|  * Write bytes/hexadecimal value of a byte array to a string.
 | |
|  * String output example :
 | |
|  * 38 00 00 00 03 63 72 65  61 74 65 20 74 61 62 6C     8....create tabl
 | |
|  * 65 20 42 6C 6F 62 54 65  73 74 63 6C 6F 62 74 65     e BlobTestclobte
 | |
|  * 73 74 32 20 28 73 74 72  6D 20 74 65 78 74 29 20     st2 (strm text)
 | |
|  * 43 48 41 52 53 45 54 20  75 74 66 38                 CHARSET utf8
 | |
|  */
 | |
| module.exports.log = function (opts, buf, off, end, header) {
 | |
|   let out = [];
 | |
|   if (!buf) return '';
 | |
|   if (off === undefined || off === null) off = 0;
 | |
|   if (end === undefined || end === null) end = buf.length;
 | |
|   let asciiValue = new Array(16);
 | |
|   asciiValue[8] = ' ';
 | |
| 
 | |
|   let useHeader = header !== undefined;
 | |
|   let offset = off || 0;
 | |
|   const maxLgh = Math.min(useHeader ? opts.debugLen - header.length : opts.debugLen, end - offset);
 | |
|   const isLimited = end - offset > maxLgh;
 | |
|   let byteValue;
 | |
|   let posHexa = 0;
 | |
|   let pos = 0;
 | |
| 
 | |
|   out.push(
 | |
|     '+--------------------------------------------------+\n' +
 | |
|       '|  0  1  2  3  4  5  6  7   8  9  a  b  c  d  e  f |\n' +
 | |
|       '+--------------------------------------------------+------------------+\n'
 | |
|   );
 | |
| 
 | |
|   if (useHeader) {
 | |
|     while (pos < header.length) {
 | |
|       if (posHexa === 0) out.push('| ');
 | |
|       byteValue = header[pos++] & 0xff;
 | |
|       out.push(hexArray[byteValue >>> 4], hexArray[byteValue & 0x0f], ' ');
 | |
|       asciiValue[posHexa++] = byteValue > 31 && byteValue < 127 ? String.fromCharCode(byteValue) : '.';
 | |
|       if (posHexa === 8) out.push(' ');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   pos = offset;
 | |
|   while (pos < maxLgh + offset) {
 | |
|     if (posHexa === 0) out.push('| ');
 | |
|     byteValue = buf[pos] & 0xff;
 | |
| 
 | |
|     out.push(hexArray[byteValue >>> 4], hexArray[byteValue & 0x0f], ' ');
 | |
| 
 | |
|     asciiValue[posHexa++] = byteValue > 31 && byteValue < 127 ? String.fromCharCode(byteValue) : '.';
 | |
| 
 | |
|     if (posHexa === 8) out.push(' ');
 | |
|     if (posHexa === 16) {
 | |
|       out.push('| ', asciiValue.join(''), ' |\n');
 | |
|       posHexa = 0;
 | |
|     }
 | |
|     pos++;
 | |
|   }
 | |
| 
 | |
|   let remaining = posHexa;
 | |
|   if (remaining > 0) {
 | |
|     if (remaining < 8) {
 | |
|       for (; remaining < 8; remaining++) {
 | |
|         out.push('   ');
 | |
|         asciiValue[posHexa++] = ' ';
 | |
|       }
 | |
|       out.push(' ');
 | |
|     }
 | |
| 
 | |
|     for (; remaining < 16; remaining++) {
 | |
|       out.push('   ');
 | |
|       asciiValue[posHexa++] = ' ';
 | |
|     }
 | |
| 
 | |
|     out.push('| ', asciiValue.join(''), isLimited ? ' |...\n' : ' |\n');
 | |
|   } else if (isLimited) {
 | |
|     out[out.length - 1] = ' |...\n';
 | |
|   }
 | |
|   out.push('+--------------------------------------------------+------------------+\n');
 | |
|   return out.join('');
 | |
| };
 | |
| 
 | |
| module.exports.toHexString = (bytes) => {
 | |
|   return Array.from(bytes, (byte) => {
 | |
|     return ('0' + (byte & 0xff).toString(16)).slice(-2);
 | |
|   }).join('');
 | |
| };
 | |
| 
 | |
| module.exports.escapeId = (opts, info, value) => {
 | |
|   if (!value || value === '') {
 | |
|     throw Errors.createError('Cannot escape empty ID value', Errors.ER_NULL_ESCAPEID, info, '0A000');
 | |
|   }
 | |
|   if (value.includes('\u0000')) {
 | |
|     throw Errors.createError(
 | |
|       'Cannot escape ID with null character (u0000)',
 | |
|       Errors.ER_NULL_CHAR_ESCAPEID,
 | |
|       info,
 | |
|       '0A000'
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   // always return escaped value, even when there is no special characters
 | |
|   // to permit working with reserved words
 | |
|   return '`' + value.replace(/`/g, '``') + '`';
 | |
| };
 | |
| 
 | |
| const escapeParameters = (opts, info, value) => {
 | |
|   if (value == null) return 'NULL';
 | |
| 
 | |
|   switch (typeof value) {
 | |
|     case 'boolean':
 | |
|       return value ? 'true' : 'false';
 | |
|     case 'bigint':
 | |
|     case 'number':
 | |
|       return `${value}`;
 | |
|     case 'object':
 | |
|       if (Object.prototype.toString.call(value) === '[object Date]') {
 | |
|         return TextEncoder.getFixedFormatDate(value);
 | |
|       } else if (Buffer.isBuffer(value)) {
 | |
|         let stValue;
 | |
|         if (Buffer.isEncoding(info.collation.charset)) {
 | |
|           stValue = value.toString(info.collation.charset, 0, value.length);
 | |
|         } else {
 | |
|           stValue = Iconv.decode(value, info.collation.charset);
 | |
|         }
 | |
|         return "_binary'" + escapeString(stValue) + "'";
 | |
|       } else if (typeof value.toSqlString === 'function') {
 | |
|         return "'" + escapeString(String(value.toSqlString())) + "'";
 | |
|       } else if (Array.isArray(value)) {
 | |
|         let out = opts.arrayParenthesis ? '(' : '';
 | |
|         for (let i = 0; i < value.length; i++) {
 | |
|           if (i !== 0) out += ',';
 | |
|           out += escapeParameters(opts, info, value[i]);
 | |
|         }
 | |
|         if (opts.arrayParenthesis) out += ')';
 | |
|         return out;
 | |
|       } else {
 | |
|         if (
 | |
|           value.type != null &&
 | |
|           [
 | |
|             'Point',
 | |
|             'LineString',
 | |
|             'Polygon',
 | |
|             'MultiPoint',
 | |
|             'MultiLineString',
 | |
|             'MultiPolygon',
 | |
|             'GeometryCollection'
 | |
|           ].includes(value.type)
 | |
|         ) {
 | |
|           //GeoJSON format.
 | |
|           let prefix =
 | |
|             info &&
 | |
|             ((info.isMariaDB() && info.hasMinVersion(10, 1, 4)) || (!info.isMariaDB() && info.hasMinVersion(5, 7, 6)))
 | |
|               ? 'ST_'
 | |
|               : '';
 | |
|           switch (value.type) {
 | |
|             case 'Point':
 | |
|               return prefix + "PointFromText('POINT(" + TextEncoder.geoPointToString(value.coordinates) + ")')";
 | |
| 
 | |
|             case 'LineString':
 | |
|               return (
 | |
|                 prefix + "LineFromText('LINESTRING(" + TextEncoder.geoArrayPointToString(value.coordinates) + ")')"
 | |
|               );
 | |
| 
 | |
|             case 'Polygon':
 | |
|               return (
 | |
|                 prefix + "PolygonFromText('POLYGON(" + TextEncoder.geoMultiArrayPointToString(value.coordinates) + ")')"
 | |
|               );
 | |
| 
 | |
|             case 'MultiPoint':
 | |
|               return (
 | |
|                 prefix +
 | |
|                 "MULTIPOINTFROMTEXT('MULTIPOINT(" +
 | |
|                 TextEncoder.geoArrayPointToString(value.coordinates) +
 | |
|                 ")')"
 | |
|               );
 | |
| 
 | |
|             case 'MultiLineString':
 | |
|               return (
 | |
|                 prefix +
 | |
|                 "MLineFromText('MULTILINESTRING(" +
 | |
|                 TextEncoder.geoMultiArrayPointToString(value.coordinates) +
 | |
|                 ")')"
 | |
|               );
 | |
| 
 | |
|             case 'MultiPolygon':
 | |
|               return (
 | |
|                 prefix + "MPolyFromText('MULTIPOLYGON(" + TextEncoder.geoMultiPolygonToString(value.coordinates) + ")')"
 | |
|               );
 | |
| 
 | |
|             case 'GeometryCollection':
 | |
|               return (
 | |
|                 prefix +
 | |
|                 "GeomCollFromText('GEOMETRYCOLLECTION(" +
 | |
|                 TextEncoder.geometricCollectionToString(value.geometries) +
 | |
|                 ")')"
 | |
|               );
 | |
|           }
 | |
|         } else {
 | |
|           if (opts.permitSetMultiParamEntries) {
 | |
|             let out = '';
 | |
|             let first = true;
 | |
|             for (let key in value) {
 | |
|               const val = value[key];
 | |
|               if (typeof val === 'function') continue;
 | |
|               if (first) {
 | |
|                 first = false;
 | |
|               } else {
 | |
|                 out += ',';
 | |
|               }
 | |
|               out += '`' + key + '`=';
 | |
|               out += this.escape(opts, info, val);
 | |
|             }
 | |
|             if (out === '') return "'" + escapeString(JSON.stringify(value)) + "'";
 | |
|             return out;
 | |
|           } else {
 | |
|             return "'" + escapeString(JSON.stringify(value)) + "'";
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     default:
 | |
|       return "'" + escapeString(value) + "'";
 | |
|   }
 | |
| };
 | |
| 
 | |
| // see https://mariadb.com/kb/en/library/string-literals/
 | |
| const LITTERAL_ESCAPE = {
 | |
|   '\u0000': '\\0',
 | |
|   "'": "\\'",
 | |
|   '"': '\\"',
 | |
|   '\b': '\\b',
 | |
|   '\n': '\\n',
 | |
|   '\r': '\\r',
 | |
|   '\t': '\\t',
 | |
|   '\u001A': '\\Z',
 | |
|   '\\': '\\\\'
 | |
| };
 | |
| 
 | |
| const CHARS_GLOBAL_REGEXP = /[\000\032"'\\\b\n\r\t]/g;
 | |
| 
 | |
| const escapeString = (val) => {
 | |
|   let offset = 0;
 | |
|   let escaped = '';
 | |
|   let match;
 | |
| 
 | |
|   while ((match = CHARS_GLOBAL_REGEXP.exec(val))) {
 | |
|     escaped += val.substring(offset, match.index);
 | |
|     escaped += LITTERAL_ESCAPE[match[0]];
 | |
|     offset = CHARS_GLOBAL_REGEXP.lastIndex;
 | |
|   }
 | |
| 
 | |
|   if (offset === 0) {
 | |
|     return val;
 | |
|   }
 | |
| 
 | |
|   if (offset < val.length) {
 | |
|     escaped += val.substring(offset);
 | |
|   }
 | |
| 
 | |
|   return escaped;
 | |
| };
 | |
| 
 | |
| module.exports.escape = escapeParameters;
 |