Skip to content

Commit 6dc7f92

Browse files
committed
Fixed some issues related to escaping
1 parent e8c0b57 commit 6dc7f92

5 files changed

Lines changed: 81 additions & 67 deletions

File tree

src/cloudsync.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,9 +1714,7 @@ int cloudsync_finalize_alter (cloudsync_context *data, cloudsync_table_context *
17141714
goto finalize;
17151715
}
17161716

1717-
char buffer[1024];
1718-
char *singlequote_escaped_table_name = sql_escape_name(table->name, buffer, sizeof(buffer));
1719-
sql = sql_build_pk_qualified_collist_query(schema, singlequote_escaped_table_name);
1717+
sql = sql_build_pk_qualified_collist_query(schema, table->name);
17201718
if (!sql) {rc = DBRES_NOMEM; goto finalize;}
17211719

17221720
char *pkclause = NULL;

src/cloudsync.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
extern "C" {
1818
#endif
1919

20-
#define CLOUDSYNC_VERSION "0.9.83"
20+
#define CLOUDSYNC_VERSION "0.9.90"
2121
#define CLOUDSYNC_MAX_TABLENAME_LEN 512
2222

2323
#define CLOUDSYNC_VALUE_NOTSET -1

src/database.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ uint64_t dbmem_size (void *ptr);
138138

139139
// SQL
140140
char *sql_build_drop_table (const char *table_name, char *buffer, int bsize, bool is_meta);
141-
char *sql_escape_name (const char *name, char *buffer, size_t bsize);
142141
char *sql_build_select_nonpk_by_pk (cloudsync_context *data, const char *table_name, const char *schema);
143142
char *sql_build_delete_by_pk (cloudsync_context *data, const char *table_name, const char *schema);
144143
char *sql_build_insert_pk_ignore (cloudsync_context *data, const char *table_name, const char *schema);

src/postgresql/database_postgresql.c

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,7 @@ static int database_refresh_snapshot (void);
8686

8787
// MARK: - SQL -
8888

89-
char *sql_build_drop_table (const char *table_name, char *buffer, int bsize, bool is_meta) {
90-
// Escape the table name (doubles any embedded quotes)
91-
char escaped[512];
92-
sql_escape_name(table_name, escaped, sizeof(escaped));
93-
94-
// Add the surrounding quotes in the format string
95-
if (is_meta) {
96-
snprintf(buffer, bsize, "DROP TABLE IF EXISTS \"%s_cloudsync\";", escaped);
97-
} else {
98-
snprintf(buffer, bsize, "DROP TABLE IF EXISTS \"%s\";", escaped);
99-
}
100-
101-
return buffer;
102-
}
103-
104-
char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
105-
// PostgreSQL identifier escaping: double any embedded double quotes
106-
// Does NOT add surrounding quotes (caller's responsibility)
107-
// Similar to SQLite's %q behavior for escaping
108-
89+
static char *sql_escape_character (const char *name, char *buffer, size_t bsize, char c) {
10990
if (!name || !buffer || bsize < 1) {
11091
if (buffer && bsize > 0) buffer[0] = '\0';
11192
return NULL;
@@ -114,14 +95,14 @@ char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
11495
size_t i = 0, j = 0;
11596

11697
while (name[i]) {
117-
if (name[i] == '"') {
118-
// Need space for 2 chars (escaped quote) + null
98+
if (name[i] == c) {
99+
// Need space for 2 chars (escaped c) + null
119100
if (j >= bsize - 2) {
120101
elog(WARNING, "Identifier name too long for buffer, truncated: %s", name);
121102
break;
122103
}
123-
buffer[j++] = '"';
124-
buffer[j++] = '"';
104+
buffer[j++] = c;
105+
buffer[j++] = c;
125106
} else {
126107
// Need space for 1 char + null
127108
if (j >= bsize - 1) {
@@ -137,6 +118,34 @@ char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
137118
return buffer;
138119
}
139120

121+
static char *sql_escape_identifier (const char *name, char *buffer, size_t bsize) {
122+
// PostgreSQL identifier escaping: double any embedded double quotes
123+
// Does NOT add surrounding quotes (caller's responsibility)
124+
// Similar to SQLite's %q behavior for escaping
125+
return sql_escape_character(name, buffer, bsize, '"');
126+
}
127+
128+
static char *sql_escape_literal (const char *name, char *buffer, size_t bsize) {
129+
// Escapes single quotes for use inside SQL string literals: ' → ''
130+
// Does NOT add surrounding quotes (caller's responsibility)
131+
return sql_escape_character(name, buffer, bsize, '\'');
132+
}
133+
134+
char *sql_build_drop_table (const char *table_name, char *buffer, int bsize, bool is_meta) {
135+
// Escape the table name (doubles any embedded quotes)
136+
char escaped[512];
137+
sql_escape_identifier(table_name, escaped, sizeof(escaped));
138+
139+
// Add the surrounding quotes in the format string
140+
if (is_meta) {
141+
snprintf(buffer, bsize, "DROP TABLE IF EXISTS \"%s_cloudsync\";", escaped);
142+
} else {
143+
snprintf(buffer, bsize, "DROP TABLE IF EXISTS \"%s\";", escaped);
144+
}
145+
146+
return buffer;
147+
}
148+
140149
char *sql_build_select_nonpk_by_pk (cloudsync_context *data, const char *table_name, const char *schema) {
141150
UNUSED_PARAMETER(data);
142151
char *qualified = database_build_base_ref(schema, table_name);
@@ -226,7 +235,7 @@ char *sql_build_rekey_pk_and_reset_version_except_col (cloudsync_context *data,
226235
return result;
227236
}
228237

229-
char *database_table_schema(const char *table_name) {
238+
char *database_table_schema (const char *table_name) {
230239
if (!table_name) return NULL;
231240

232241
// Build metadata table name
@@ -279,31 +288,31 @@ char *database_table_schema(const char *table_name) {
279288
return schema;
280289
}
281290

282-
char *database_build_meta_ref(const char *schema, const char *table_name) {
291+
char *database_build_meta_ref (const char *schema, const char *table_name) {
283292
char escaped_table[512];
284-
sql_escape_name(table_name, escaped_table, sizeof(escaped_table));
293+
sql_escape_identifier(table_name, escaped_table, sizeof(escaped_table));
285294
if (schema) {
286295
char escaped_schema[512];
287-
sql_escape_name(schema, escaped_schema, sizeof(escaped_schema));
296+
sql_escape_identifier(schema, escaped_schema, sizeof(escaped_schema));
288297
return cloudsync_memory_mprintf("\"%s\".\"%s_cloudsync\"", escaped_schema, escaped_table);
289298
}
290299
return cloudsync_memory_mprintf("\"%s_cloudsync\"", escaped_table);
291300
}
292301

293-
char *database_build_base_ref(const char *schema, const char *table_name) {
302+
char *database_build_base_ref (const char *schema, const char *table_name) {
294303
char escaped_table[512];
295-
sql_escape_name(table_name, escaped_table, sizeof(escaped_table));
304+
sql_escape_identifier(table_name, escaped_table, sizeof(escaped_table));
296305
if (schema) {
297306
char escaped_schema[512];
298-
sql_escape_name(schema, escaped_schema, sizeof(escaped_schema));
307+
sql_escape_identifier(schema, escaped_schema, sizeof(escaped_schema));
299308
return cloudsync_memory_mprintf("\"%s\".\"%s\"", escaped_schema, escaped_table);
300309
}
301310
return cloudsync_memory_mprintf("\"%s\"", escaped_table);
302311
}
303312

304313
// Schema-aware SQL builder for PostgreSQL: deletes columns not in schema or pkcol.
305314
// Schema parameter: pass empty string to fall back to current_schema() via SQL.
306-
char *sql_build_delete_cols_not_in_schema_query(const char *schema, const char *table_name, const char *meta_ref, const char *pkcol) {
315+
char *sql_build_delete_cols_not_in_schema_query (const char *schema, const char *table_name, const char *meta_ref, const char *pkcol) {
307316
const char *schema_param = schema ? schema : "";
308317
return cloudsync_memory_mprintf(
309318
"DELETE FROM %s WHERE col_name NOT IN ("
@@ -316,7 +325,7 @@ char *sql_build_delete_cols_not_in_schema_query(const char *schema, const char *
316325
}
317326

318327
// Builds query to get comma-separated list of primary key column names.
319-
char *sql_build_pk_collist_query(const char *schema, const char *table_name) {
328+
char *sql_build_pk_collist_query (const char *schema, const char *table_name) {
320329
const char *schema_param = schema ? schema : "";
321330
return cloudsync_memory_mprintf(
322331
"SELECT string_agg(quote_ident(column_name), ',') "
@@ -328,7 +337,7 @@ char *sql_build_pk_collist_query(const char *schema, const char *table_name) {
328337
}
329338

330339
// Builds query to get SELECT list of decoded primary key columns.
331-
char *sql_build_pk_decode_selectlist_query(const char *schema, const char *table_name) {
340+
char *sql_build_pk_decode_selectlist_query (const char *schema, const char *table_name) {
332341
const char *schema_param = schema ? schema : "";
333342
return cloudsync_memory_mprintf(
334343
"SELECT string_agg("
@@ -342,14 +351,18 @@ char *sql_build_pk_decode_selectlist_query(const char *schema, const char *table
342351
}
343352

344353
// Builds query to get qualified (schema.table.column) primary key column list.
345-
char *sql_build_pk_qualified_collist_query(const char *schema, const char *table_name) {
354+
char *sql_build_pk_qualified_collist_query (const char *schema, const char *table_name) {
346355
const char *schema_param = schema ? schema : "";
356+
357+
char buffer[1024];
358+
char *singlequote_escaped_table_name = sql_escape_literal(table_name, buffer, sizeof(buffer));
359+
if (!singlequote_escaped_table_name) return NULL;
360+
347361
return cloudsync_memory_mprintf(
348362
"SELECT string_agg(quote_ident(column_name), ',' ORDER BY ordinal_position) "
349363
"FROM information_schema.key_column_usage "
350364
"WHERE table_name = '%s' AND table_schema = COALESCE(NULLIF('%s', ''), current_schema()) "
351-
"AND constraint_name LIKE '%%_pkey';",
352-
table_name, schema_param
365+
"AND constraint_name LIKE '%%_pkey';", singlequote_escaped_table_name, schema_param
353366
);
354367
}
355368

@@ -1116,7 +1129,7 @@ static int database_create_insert_trigger_internal (cloudsync_context *data, con
11161129
char trigger_name[1024];
11171130
char func_name[1024];
11181131
char escaped_tbl[512];
1119-
sql_escape_name(table_name, escaped_tbl, sizeof(escaped_tbl));
1132+
sql_escape_identifier(table_name, escaped_tbl, sizeof(escaped_tbl));
11201133
snprintf(trigger_name, sizeof(trigger_name), "cloudsync_after_insert_%s", escaped_tbl);
11211134
snprintf(func_name, sizeof(func_name), "cloudsync_after_insert_%s_fn", escaped_tbl);
11221135

@@ -1178,7 +1191,7 @@ static int database_create_update_trigger_gos_internal (cloudsync_context *data,
11781191
char trigger_name[1024];
11791192
char func_name[1024];
11801193
char escaped_tbl[512];
1181-
sql_escape_name(table_name, escaped_tbl, sizeof(escaped_tbl));
1194+
sql_escape_identifier(table_name, escaped_tbl, sizeof(escaped_tbl));
11821195
snprintf(trigger_name, sizeof(trigger_name), "cloudsync_before_update_%s", escaped_tbl);
11831196
snprintf(func_name, sizeof(func_name), "cloudsync_before_update_%s_fn", escaped_tbl);
11841197

@@ -1221,7 +1234,7 @@ static int database_create_update_trigger_internal (cloudsync_context *data, con
12211234
char trigger_name[1024];
12221235
char func_name[1024];
12231236
char escaped_tbl[512];
1224-
sql_escape_name(table_name, escaped_tbl, sizeof(escaped_tbl));
1237+
sql_escape_identifier(table_name, escaped_tbl, sizeof(escaped_tbl));
12251238
snprintf(trigger_name, sizeof(trigger_name), "cloudsync_after_update_%s", escaped_tbl);
12261239
snprintf(func_name, sizeof(func_name), "cloudsync_after_update_%s_fn", escaped_tbl);
12271240

@@ -1327,7 +1340,7 @@ static int database_create_delete_trigger_gos_internal (cloudsync_context *data,
13271340
char trigger_name[1024];
13281341
char func_name[1024];
13291342
char escaped_tbl[512];
1330-
sql_escape_name(table_name, escaped_tbl, sizeof(escaped_tbl));
1343+
sql_escape_identifier(table_name, escaped_tbl, sizeof(escaped_tbl));
13311344
snprintf(trigger_name, sizeof(trigger_name), "cloudsync_before_delete_%s", escaped_tbl);
13321345
snprintf(func_name, sizeof(func_name), "cloudsync_before_delete_%s_fn", escaped_tbl);
13331346

@@ -1370,7 +1383,7 @@ static int database_create_delete_trigger_internal (cloudsync_context *data, con
13701383
char trigger_name[1024];
13711384
char func_name[1024];
13721385
char escaped_tbl[512];
1373-
sql_escape_name(table_name, escaped_tbl, sizeof(escaped_tbl));
1386+
sql_escape_identifier(table_name, escaped_tbl, sizeof(escaped_tbl));
13741387
snprintf(trigger_name, sizeof(trigger_name), "cloudsync_after_delete_%s", escaped_tbl);
13751388
snprintf(func_name, sizeof(func_name), "cloudsync_after_delete_%s_fn", escaped_tbl);
13761389

@@ -1470,7 +1483,7 @@ int database_delete_triggers (cloudsync_context *data, const char *table) {
14701483
if (!base_ref) return DBRES_NOMEM;
14711484

14721485
char escaped_tbl[512];
1473-
sql_escape_name(table, escaped_tbl, sizeof(escaped_tbl));
1486+
sql_escape_identifier(table, escaped_tbl, sizeof(escaped_tbl));
14741487

14751488
char *sql = cloudsync_memory_mprintf(
14761489
"DROP TRIGGER IF EXISTS \"cloudsync_after_insert_%s\" ON %s;",

src/sqlite/database_sqlite.c

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ char *sql_build_drop_table (const char *table_name, char *buffer, int bsize, boo
4141
return sql;
4242
}
4343

44-
char *sql_escape_name (const char *name, char *buffer, size_t bsize) {
44+
char *sql_escape_identifier (const char *name, char *buffer, size_t bsize) {
4545
return sqlite3_snprintf((int)bsize, buffer, "%q", name);
4646
}
4747

@@ -77,7 +77,7 @@ char *sql_build_select_nonpk_by_pk (cloudsync_context *data, const char *table_n
7777
// Unfortunately in SQLite column names (or table names) cannot be bound parameters in a SELECT statement
7878
// otherwise we should have used something like SELECT 'SELECT ? FROM %w WHERE rowid=?';
7979
char buffer[1024];
80-
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
80+
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
8181

8282
#if !CLOUDSYNC_DISABLE_ROWIDONLY_TABLES
8383
if (table->rowid_only) {
@@ -103,7 +103,7 @@ char *sql_build_select_nonpk_by_pk (cloudsync_context *data, const char *table_n
103103
char *sql_build_delete_by_pk (cloudsync_context *data, const char *table_name, const char *schema) {
104104
UNUSED_PARAMETER(schema);
105105
char buffer[1024];
106-
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
106+
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
107107
char *sql = cloudsync_memory_mprintf(SQL_BUILD_DELETE_ROW_BY_PK, table_name, singlequote_escaped_table_name);
108108
if (!sql) return NULL;
109109

@@ -117,7 +117,7 @@ char *sql_build_delete_by_pk (cloudsync_context *data, const char *table_name, c
117117
char *sql_build_insert_pk_ignore (cloudsync_context *data, const char *table_name, const char *schema) {
118118
UNUSED_PARAMETER(schema);
119119
char buffer[1024];
120-
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
120+
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
121121
char *sql = cloudsync_memory_mprintf(SQL_BUILD_INSERT_PK_IGNORE, table_name, table_name, singlequote_escaped_table_name);
122122
if (!sql) return NULL;
123123

@@ -132,8 +132,8 @@ char *sql_build_upsert_pk_and_col (cloudsync_context *data, const char *table_na
132132
UNUSED_PARAMETER(schema);
133133
char buffer[1024];
134134
char buffer2[1024];
135-
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
136-
char *singlequote_escaped_col_name = sql_escape_name(colname, buffer2, sizeof(buffer2));
135+
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
136+
char *singlequote_escaped_col_name = sql_escape_identifier(colname, buffer2, sizeof(buffer2));
137137
char *sql = cloudsync_memory_mprintf(
138138
SQL_BUILD_UPSERT_PK_AND_COL,
139139
table_name,
@@ -156,8 +156,8 @@ char *sql_build_select_cols_by_pk (cloudsync_context *data, const char *table_na
156156
char *colnamequote = "\"";
157157
char buffer[1024];
158158
char buffer2[1024];
159-
char *singlequote_escaped_table_name = sql_escape_name(table_name, buffer, sizeof(buffer));
160-
char *singlequote_escaped_col_name = sql_escape_name(colname, buffer2, sizeof(buffer2));
159+
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
160+
char *singlequote_escaped_col_name = sql_escape_identifier(colname, buffer2, sizeof(buffer2));
161161
char *sql = cloudsync_memory_mprintf(
162162
SQL_BUILD_SELECT_COLS_BY_PK_FMT,
163163
table_name,
@@ -186,33 +186,33 @@ char *sql_build_rekey_pk_and_reset_version_except_col (cloudsync_context *data,
186186
return result;
187187
}
188188

189-
char *database_table_schema(const char *table_name) {
189+
char *database_table_schema (const char *table_name) {
190190
return NULL;
191191
}
192192

193-
char *database_build_meta_ref(const char *schema, const char *table_name) {
193+
char *database_build_meta_ref (const char *schema, const char *table_name) {
194194
// schema unused in SQLite
195195
return cloudsync_memory_mprintf("%s_cloudsync", table_name);
196196
}
197197

198-
char *database_build_base_ref(const char *schema, const char *table_name) {
198+
char *database_build_base_ref (const char *schema, const char *table_name) {
199199
// schema unused in SQLite
200200
return cloudsync_string_dup(table_name);
201201
}
202202

203203
// SQLite version: schema parameter unused (SQLite has no schemas).
204-
char *sql_build_delete_cols_not_in_schema_query(const char *schema, const char *table_name, const char *meta_ref, const char *pkcol) {
204+
char *sql_build_delete_cols_not_in_schema_query (const char *schema, const char *table_name, const char *meta_ref, const char *pkcol) {
205205
UNUSED_PARAMETER(schema);
206206
return cloudsync_memory_mprintf(
207-
"DELETE FROM %s WHERE col_name NOT IN ("
208-
"SELECT name FROM pragma_table_info('%s') "
207+
"DELETE FROM \"%w\" WHERE col_name NOT IN ("
208+
"SELECT name FROM pragma_table_info('%q') "
209209
"UNION SELECT '%s'"
210210
");",
211211
meta_ref, table_name, pkcol
212212
);
213213
}
214214

215-
char *sql_build_pk_collist_query(const char *schema, const char *table_name) {
215+
char *sql_build_pk_collist_query (const char *schema, const char *table_name) {
216216
UNUSED_PARAMETER(schema);
217217
return cloudsync_memory_mprintf(
218218
"SELECT group_concat('\"' || format('%%w', name) || '\"', ',') "
@@ -221,7 +221,7 @@ char *sql_build_pk_collist_query(const char *schema, const char *table_name) {
221221
);
222222
}
223223

224-
char *sql_build_pk_decode_selectlist_query(const char *schema, const char *table_name) {
224+
char *sql_build_pk_decode_selectlist_query (const char *schema, const char *table_name) {
225225
UNUSED_PARAMETER(schema);
226226
return cloudsync_memory_mprintf(
227227
"SELECT group_concat("
@@ -232,12 +232,16 @@ char *sql_build_pk_decode_selectlist_query(const char *schema, const char *table
232232
);
233233
}
234234

235-
char *sql_build_pk_qualified_collist_query(const char *schema, const char *table_name) {
235+
char *sql_build_pk_qualified_collist_query (const char *schema, const char *table_name) {
236236
UNUSED_PARAMETER(schema);
237+
238+
char buffer[1024];
239+
char *singlequote_escaped_table_name = sql_escape_identifier(table_name, buffer, sizeof(buffer));
240+
if (!singlequote_escaped_table_name) return NULL;
241+
237242
return cloudsync_memory_mprintf(
238243
"SELECT group_concat('\"%w\".\"' || format('%%w', name) || '\"', ',') "
239-
"FROM pragma_table_info('%s') WHERE pk>0 ORDER BY pk;",
240-
table_name, table_name
244+
"FROM pragma_table_info('%s') WHERE pk>0 ORDER BY pk;", singlequote_escaped_table_name, singlequote_escaped_table_name
241245
);
242246
}
243247

0 commit comments

Comments
 (0)