🙊 escape sql strings
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import type {
|
||||
IChartEventFilter,
|
||||
IGetChartDataInput,
|
||||
} from '@openpanel/validation';
|
||||
|
||||
import { formatClickhouseDate } from '../clickhouse-client';
|
||||
import type { SqlBuilderObject } from '../sql-builder';
|
||||
import { createSqlBuilder } from '../sql-builder';
|
||||
|
||||
function log(sql: string) {
|
||||
@@ -25,10 +26,10 @@ export function getChartSql({
|
||||
createSqlBuilder();
|
||||
|
||||
sb.where = getEventFiltersWhereClause(event.filters);
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
if (event.name !== '*') {
|
||||
sb.select.label = `'${event.name}' as label`;
|
||||
sb.where.eventName = `name = '${event.name}'`;
|
||||
sb.select.label = `${escape(event.name)} as label`;
|
||||
sb.where.eventName = `name = ${escape(event.name)}`;
|
||||
}
|
||||
|
||||
sb.select.count = `count(*) as count`;
|
||||
@@ -64,10 +65,10 @@ export function getChartSql({
|
||||
const breakdown = breakdowns[0]!;
|
||||
if (breakdown) {
|
||||
const value = breakdown.name.startsWith('properties.')
|
||||
? `mapValues(mapExtractKeyLike(properties, '${breakdown.name
|
||||
.replace(/^properties\./, '')
|
||||
.replace('.*.', '.%.')}'))`
|
||||
: breakdown.name;
|
||||
? `mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
breakdown.name.replace(/^properties\./, '').replace('.*.', '.%.')
|
||||
)}))`
|
||||
: escape(breakdown.name);
|
||||
sb.select.label = breakdown.name.startsWith('properties.')
|
||||
? `arrayElement(${value}, 1) as label`
|
||||
: `${breakdown.name} as label`;
|
||||
@@ -120,32 +121,32 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
if (value.length === 0) return;
|
||||
|
||||
if (name.startsWith('properties.')) {
|
||||
const whereFrom = `mapValues(mapExtractKeyLike(properties, '${name
|
||||
.replace(/^properties\./, '')
|
||||
.replace('.*.', '.%.')}'))`;
|
||||
const whereFrom = `mapValues(mapExtractKeyLike(properties, ${escape(
|
||||
name.replace(/^properties\./, '').replace('.*.', '.%.')
|
||||
)}))`;
|
||||
|
||||
switch (operator) {
|
||||
case 'is': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x = '${String(val).trim()}'`)
|
||||
.map((val) => `x = ${escape(String(val).trim())}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
case 'isNot': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x != '${String(val).trim()}'`)
|
||||
.map((val) => `x != ${escape(String(val).trim())}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
case 'contains': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x LIKE '%${String(val).trim()}%'`)
|
||||
.map((val) => `x LIKE ${escape(`%${String(val).trim()}%`)}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
case 'doesNotContain': {
|
||||
where[id] = `arrayExists(x -> ${value
|
||||
.map((val) => `x NOT LIKE '%${String(val).trim()}%'`)
|
||||
.map((val) => `x NOT LIKE ${escape(`%${String(val).trim()}%`)}`)
|
||||
.join(' OR ')}, ${whereFrom})`;
|
||||
break;
|
||||
}
|
||||
@@ -154,25 +155,27 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
|
||||
switch (operator) {
|
||||
case 'is': {
|
||||
where[id] = `${name} IN (${value
|
||||
.map((val) => `'${String(val).trim()}'`)
|
||||
.map((val) => escape(String(val).trim()))
|
||||
.join(', ')})`;
|
||||
break;
|
||||
}
|
||||
case 'isNot': {
|
||||
where[id] = `${name} NOT IN (${value
|
||||
.map((val) => `'${String(val).trim()}'`)
|
||||
.map((val) => escape(String(val).trim()))
|
||||
.join(', ')})`;
|
||||
break;
|
||||
}
|
||||
case 'contains': {
|
||||
where[id] = value
|
||||
.map((val) => `${name} LIKE '%${String(val).trim()}%'`)
|
||||
.map((val) => `${name} LIKE ${escape(`%${String(val).trim()}%`)}`)
|
||||
.join(' OR ');
|
||||
break;
|
||||
}
|
||||
case 'doesNotContain': {
|
||||
where[id] = value
|
||||
.map((val) => `${name} NOT LIKE '%${String(val).trim()}%'`)
|
||||
.map(
|
||||
(val) => `${name} NOT LIKE ${escape(`%${String(val).trim()}%`)}`
|
||||
)
|
||||
.join(' OR ');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { omit, uniq } from 'ramda';
|
||||
import { escape } from 'sqlstring';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { randomSplitName, toDots } from '@openpanel/common';
|
||||
@@ -261,15 +262,15 @@ export async function getEventList({
|
||||
|
||||
sb.limit = take;
|
||||
sb.offset = Math.max(0, (cursor ?? 0) * take);
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
|
||||
if (profileId) {
|
||||
sb.where.deviceId = `device_id IN (SELECT device_id as did FROM openpanel.events WHERE profile_id = '${profileId}' group by did)`;
|
||||
sb.where.deviceId = `device_id IN (SELECT device_id as did FROM openpanel.events WHERE profile_id = ${escape(profileId)} group by did)`;
|
||||
}
|
||||
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((n) => `'${n}'`),
|
||||
events.map((event) => escape(event)),
|
||||
','
|
||||
)})`;
|
||||
}
|
||||
@@ -297,14 +298,14 @@ export async function getEventsCount({
|
||||
filters,
|
||||
}: Omit<GetEventListOptions, 'cursor' | 'take'>) {
|
||||
const { sb, getSql, join } = createSqlBuilder();
|
||||
sb.where.projectId = `project_id = '${projectId}'`;
|
||||
sb.where.projectId = `project_id = ${escape(projectId)}`;
|
||||
if (profileId) {
|
||||
sb.where.profileId = `profile_id = '${profileId}'`;
|
||||
sb.where.profileId = `profile_id = ${escape(profileId)}`;
|
||||
}
|
||||
|
||||
if (events && events.length > 0) {
|
||||
sb.where.events = `name IN (${join(
|
||||
events.map((n) => `'${n}'`),
|
||||
events.map((event) => escape(event)),
|
||||
','
|
||||
)})`;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { escape } from 'sqlstring';
|
||||
|
||||
import { toDots, toObject } from '@openpanel/common';
|
||||
import type { IChartEventFilter } from '@openpanel/validation';
|
||||
|
||||
@@ -11,7 +13,7 @@ export async function getProfileById(id: string) {
|
||||
}
|
||||
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT *, created_at as max_created_at FROM profiles WHERE id = '${id}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT *, created_at as max_created_at FROM profiles WHERE id = ${escape(id)} ORDER BY created_at DESC LIMIT 1`
|
||||
);
|
||||
|
||||
if (!profile) {
|
||||
@@ -53,7 +55,7 @@ export async function getProfiles({ ids }: GetProfilesOptions) {
|
||||
`SELECT
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
WHERE id IN (${ids.map((id) => `'${id}'`).join(',')})
|
||||
WHERE id IN (${ids.map((id) => escape(id)).join(',')})
|
||||
GROUP BY id
|
||||
`
|
||||
);
|
||||
@@ -66,7 +68,7 @@ function getProfileInnerSelect(projectId: string) {
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
GROUP BY id
|
||||
HAVING project_id = '${projectId}')`;
|
||||
HAVING project_id = ${escape(projectId)})`;
|
||||
}
|
||||
|
||||
export async function getProfileList({
|
||||
@@ -120,7 +122,7 @@ export async function getProfilesByExternalId(
|
||||
${getProfileSelectFields()}
|
||||
FROM profiles
|
||||
GROUP BY id
|
||||
HAVING project_id = '${projectId}' AND external_id = '${externalId}'
|
||||
HAVING project_id = ${escape(projectId)} AND external_id = ${escape(externalId)}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -192,7 +194,7 @@ export async function upsertProfile({
|
||||
projectId,
|
||||
}: IServiceUpsertProfile) {
|
||||
const [profile] = await chQuery<IClickhouseProfile>(
|
||||
`SELECT * FROM profiles WHERE id = '${id}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||
`SELECT * FROM profiles WHERE id = ${escape(id)} AND project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 1`
|
||||
);
|
||||
|
||||
await ch.insert({
|
||||
|
||||
Reference in New Issue
Block a user