UNPKG

5.51 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2018 Google LLC
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18'use strict';
19
20/* eslint id-length: 0, complexity: ["error", { "max": 15 }] */
21
22const { Mintable } = require('node-sec-patterns');
23const { SqlFragment } = require('./fragment.js');
24const { SqlId } = require('./id.js');
25
26const isSqlId = Mintable.verifierFor(SqlId);
27const isSqlFragment = Mintable.verifierFor(SqlFragment);
28
29const iteratorSymbol = Symbol.iterator;
30const { isArray } = Array;
31const { apply } = Reflect;
32const { toString: bufferProtoToString } = Buffer.prototype;
33const { isBuffer } = Buffer;
34
35const CHARS_GLOBAL_REGEXP = /[\0\b\t\n\r\x1a"'\\$]/g; // eslint-disable-line no-control-regex
36const TZ_REGEXP = /([+\-\s])(\d\d):?(\d\d)?/;
37
38function isSeries(val) {
39 // The typeof val === 'object' check prevents treating strings as series.
40 // Per (6.1.5.1 Well-Known Symbols),
41 // "Unless otherwise specified, well-known symbols values are shared by all realms"
42 // so the iteratorSymbol check below should work cross-realm.
43 // TODO: It's possible that a function might implement iterator.
44 return val && typeof val !== 'string' && (isArray(val) || typeof val[iteratorSymbol] === 'function');
45}
46
47function pad(val, template) {
48 const str = `${ val >>> 0 }`; // eslint-disable-line no-bitwise
49 return `${ template.substring(str.length) }${ str }`;
50}
51
52function convertTimezone(tz) {
53 if (tz === 'Z') {
54 return 0;
55 }
56
57 const m = TZ_REGEXP.exec(tz);
58 if (m) {
59 // eslint-disable-next-line no-magic-numbers
60 return (m[1] === '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
61 }
62 return false;
63}
64
65function escapeSeries(series, escapeOne, nests) {
66 let sql = '';
67
68 if (isArray(series)) {
69 for (let i = 0, len = series.length; i < len; ++i) {
70 const val = series[i];
71 if (nests && isSeries(val)) {
72 sql += `${ (i ? ', (' : '(') }${ escapeSeries(val, escapeOne, true) })`;
73 } else {
74 sql += `${ (i ? ', ' : '') }${ escapeOne(val) }`;
75 }
76 }
77 } else {
78 let wrote = false;
79 for (const val of series) {
80 if (nests && isSeries(val)) {
81 sql += `${ (wrote ? ', (' : '(') }${ escapeSeries(val, escapeOne, true) })`;
82 } else {
83 sql += `${ (wrote ? ', ' : '') }${ escapeOne(val) }`;
84 }
85 wrote = true;
86 }
87 }
88
89 return sql;
90}
91
92function bufferToString(buffer) {
93 return `X'${ apply(bufferProtoToString, buffer, [ 'hex' ]) }'`;
94}
95
96
97function makeEscaper(escapeId, escapeString) {
98 // eslint-disable-next-line max-params
99 function formatDate(year, month, day, hour, minute, second, millis) {
100 // YYYY-MM-DD HH:mm:ss.mmm
101 return escapeString(`${ pad(year, '0000') }-${ pad(month, '00') }-${ pad(day, '00') } ${ pad(hour, '00')
102 }:${ pad(minute, '00') }:${ pad(second, '00') }.${ pad(millis, '000') }`);
103 }
104
105 function dateToString(date, timeZone) {
106 const dt = new Date(date);
107
108 if (isNaN(dt.getTime())) {
109 return 'NULL';
110 }
111
112 if (timeZone === 'local') {
113 return formatDate(
114 dt.getFullYear(),
115 dt.getMonth() + 1,
116 dt.getDate(),
117 dt.getHours(),
118 dt.getMinutes(),
119 dt.getSeconds(),
120 dt.getMilliseconds());
121 }
122
123 const tz = convertTimezone(timeZone);
124
125 if (tz !== false && tz !== 0) {
126 // eslint-disable-next-line no-magic-numbers
127 dt.setTime(dt.getTime() + (tz * 60000));
128 }
129
130 return formatDate(
131 dt.getUTCFullYear(),
132 dt.getUTCMonth() + 1,
133 dt.getUTCDate(),
134 dt.getUTCHours(),
135 dt.getUTCMinutes(),
136 dt.getUTCSeconds(),
137 dt.getUTCMilliseconds());
138 }
139
140 function escape(val, stringifyObjects, timeZone) {
141 if (val === void 0 || val === null) {
142 return 'NULL';
143 }
144
145 switch (typeof val) {
146 case 'boolean':
147 return (val) ? 'true' : 'false';
148 case 'number':
149 return `${ val }`;
150 case 'object':
151 break;
152 default:
153 return escapeString(val);
154 }
155 if (isSqlFragment(val)) {
156 return val.content;
157 }
158 if (isSqlId(val)) {
159 return escapeId(val.content);
160 }
161 if (val instanceof Date) {
162 return dateToString(val, timeZone || 'local');
163 }
164 if (isBuffer(val)) {
165 return bufferToString(val);
166 }
167 if (isSeries(val)) {
168 return escapeSeries(val, (element) => escape(element, true, timeZone), true);
169 }
170 if (stringifyObjects) {
171 return escapeString(val.toString());
172 }
173 // eslint-disable-next-line no-use-before-define
174 return objectToValues(val, timeZone);
175 }
176
177 function objectToValues(obj, timeZone) {
178 let sql = '';
179
180 for (const key in obj) {
181 const val = obj[key];
182
183 if (typeof val === 'function') {
184 continue;
185 }
186
187 sql += `${ (sql.length === 0 ? '' : ', ') + escapeId(key) } = ${ escape(val, true, timeZone) }`;
188 }
189
190 return sql;
191 }
192
193 return escape;
194}
195
196module.exports = Object.freeze({
197 CHARS_GLOBAL_REGEXP,
198 escapeSeries,
199 isSeries,
200 isSqlFragment,
201 makeEscaper,
202});