UNPKG

@nuxt/content

Version:

Write your content inside your Nuxt app

93 lines (92 loc) 3.49 kB
const SQL_COMMANDS = /SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|\$/i; const SQL_COUNT_REGEX = /COUNT\((DISTINCT )?([a-z_]\w+|\*)\)/i; const SQL_SELECT_REGEX = /^SELECT (.*) FROM (\w+)( WHERE .*)? ORDER BY (["\w,\s]+) (ASC|DESC)( LIMIT \d+)?( OFFSET \d+)?$/; export function assertSafeQuery(sql, collection) { if (!sql) { throw new Error("Invalid query: Query cannot be empty"); } const cleanedupQuery = cleanupQuery(sql); if (cleanedupQuery !== sql) { throw new Error("Invalid query: SQL comments are not allowed"); } const match = sql.match(SQL_SELECT_REGEX); if (!match) { throw new Error("Invalid query: Query must be a valid SELECT statement with proper syntax"); } const [_, select, from, where, orderBy, order, limit, offset] = match; const columns = select?.trim().split(", ") || []; if (columns.length === 1) { if (columns[0] !== "*" && !columns[0]?.match(SQL_COUNT_REGEX) && !columns[0]?.match(/^"[a-z_]\w+"$/i)) { throw new Error(`Invalid query: Column '${columns[0]}' has invalid format. Expected *, COUNT(), or a quoted column name`); } } else if (!columns.every((column) => column.match(/^"[a-z_]\w+"$/i))) { throw new Error("Invalid query: Multiple columns must be properly quoted and alphanumeric"); } if (from !== `_content_${collection}`) { const collection2 = String(from || "").replace(/^_content_/, ""); throw new Error(`Invalid query: Collection '${collection2}' does not exist`); } if (where) { if (!where.startsWith(" WHERE (") || !where.endsWith(")")) { throw new Error("Invalid query: WHERE clause must be properly enclosed in parentheses"); } const noString = cleanupQuery(where, { removeString: true }); if (noString.match(SQL_COMMANDS)) { throw new Error("Invalid query: WHERE clause contains unsafe SQL commands"); } } const _order = (orderBy + " " + order).split(", "); if (!_order.every((column) => column.match(/^("[a-zA-Z_]+"|[a-zA-Z_]+) (ASC|DESC)$/))) { throw new Error("Invalid query: ORDER BY clause must contain valid column names followed by ASC or DESC"); } if (limit !== void 0 && !limit.match(/^ LIMIT \d+$/)) { throw new Error("Invalid query: LIMIT clause must be a positive number"); } if (offset !== void 0 && !offset.match(/^ OFFSET \d+$/)) { throw new Error("Invalid query: OFFSET clause must be a positive number"); } return true; } function cleanupQuery(query, options = { removeString: false }) { let inString = false; let stringFence = ""; let result = ""; for (let i = 0; i < query.length; i++) { const char = query[i]; const prevChar = query[i - 1]; const nextChar = query[i + 1]; if (char === "'" || char === '"') { if (!options?.removeString) { result += char; continue; } if (inString) { if (char !== stringFence || nextChar === stringFence || prevChar === stringFence) { continue; } inString = false; stringFence = ""; continue; } else { inString = true; stringFence = char; continue; } } if (!inString) { if (char === "-" && nextChar === "-") { return result; } if (char === "/" && nextChar === "*") { i += 2; while (i < query.length && !(query[i] === "*" && query[i + 1] === "/")) { i += 1; } i += 2; continue; } result += char; } } return result; }