From 177e19632a3ebe81eea8444d571def4da50017a5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 20 Feb 2020 07:18:16 +0900
Subject: [PATCH] Fix #6016

---
 src/misc/safe-for-sql.ts                        | 3 +++
 src/server/api/endpoints/hashtags/trend.ts      | 5 +++--
 src/server/api/endpoints/notes/search-by-tag.ts | 5 +++--
 3 files changed, 9 insertions(+), 4 deletions(-)
 create mode 100644 src/misc/safe-for-sql.ts

diff --git a/src/misc/safe-for-sql.ts b/src/misc/safe-for-sql.ts
new file mode 100644
index 0000000000..7a57fabdfc
--- /dev/null
+++ b/src/misc/safe-for-sql.ts
@@ -0,0 +1,3 @@
+export function safeForSql(text: string): boolean {
+	return /[\0\x08\x09\x1a\n\r"'\\\%]/g.test(text);
+}
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index 740b6de4d7..cfa97d1475 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -3,6 +3,7 @@ import define from '../../define';
 import { fetchMeta } from '../../../../misc/fetch-meta';
 import { Notes } from '../../../../models';
 import { Note } from '../../../../models/entities/note';
+import { safeForSql } from '../../../../misc/safe-for-sql';
 
 /*
 トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
@@ -113,7 +114,7 @@ export default define(meta, async () => {
 	for (let i = 0; i < range; i++) {
 		countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note')
 			.select('count(distinct note.userId)')
-			.where(':tag = ANY(note.tags)', { tag: tag })
+			.where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`)
 			.andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) })
 			.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) })
 			.cache(60000) // 1 min
@@ -127,7 +128,7 @@ export default define(meta, async () => {
 
 	const totalCounts = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note')
 		.select('count(distinct note.userId)')
-		.where(':tag = ANY(note.tags)', { tag: tag })
+		.where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`)
 		.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) })
 		.cache(60000 * 60) // 60 min
 		.getRawOne()
diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts
index f4b89ff9f5..aaeec5ecf4 100644
--- a/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/src/server/api/endpoints/notes/search-by-tag.ts
@@ -99,7 +99,8 @@ export default define(meta, async (ps, me) => {
 	if (me) generateMuteQuery(query, me);
 
 	if (ps.tag) {
-		query.andWhere(':tag = ANY(note.tags)', { tag: ps.tag.toLowerCase() });
+		if (/[\0\x08\x09\x1a\n\r"'\\\%]/g.test(ps.tag)) return;
+		query.andWhere(`'{"${ps.tag.toLowerCase()}"}' <@ note.tags`);
 	} else {
 		let i = 0;
 		query.andWhere(new Brackets(qb => {
@@ -143,7 +144,7 @@ export default define(meta, async (ps, me) => {
 	}
 
 	// Search notes
-	const notes = await query.take(ps.limit!).getMany();
+	const notes = await query.take(ps.limit!).printSql().getMany();
 
 	return await Notes.packMany(notes, me);
 });