26
26
#include "util.h"
27
27
28
28
/* prototypes */
29
- static int pysqlite_check_remaining_sql (const char * tail );
30
-
31
- typedef enum {
32
- LINECOMMENT_1 ,
33
- IN_LINECOMMENT ,
34
- COMMENTSTART_1 ,
35
- IN_COMMENT ,
36
- COMMENTEND_1 ,
37
- NORMAL
38
- } parse_remaining_sql_state ;
29
+ static const char * lstrip_sql (const char * sql );
39
30
40
31
pysqlite_Statement *
41
32
pysqlite_statement_create (pysqlite_Connection * connection , PyObject * sql )
@@ -73,7 +64,7 @@ pysqlite_statement_create(pysqlite_Connection *connection, PyObject *sql)
73
64
return NULL ;
74
65
}
75
66
76
- if (pysqlite_check_remaining_sql (tail )) {
67
+ if (lstrip_sql (tail ) != NULL ) {
77
68
PyErr_SetString (connection -> ProgrammingError ,
78
69
"You can only execute one statement at a time." );
79
70
goto error ;
@@ -82,20 +73,12 @@ pysqlite_statement_create(pysqlite_Connection *connection, PyObject *sql)
82
73
/* Determine if the statement is a DML statement.
83
74
SELECT is the only exception. See #9924. */
84
75
int is_dml = 0 ;
85
- for (const char * p = sql_cstr ; * p != 0 ; p ++ ) {
86
- switch (* p ) {
87
- case ' ' :
88
- case '\r' :
89
- case '\n' :
90
- case '\t' :
91
- continue ;
92
- }
93
-
76
+ const char * p = lstrip_sql (sql_cstr );
77
+ if (p != NULL ) {
94
78
is_dml = (PyOS_strnicmp (p , "insert" , 6 ) == 0 )
95
79
|| (PyOS_strnicmp (p , "update" , 6 ) == 0 )
96
80
|| (PyOS_strnicmp (p , "delete" , 6 ) == 0 )
97
81
|| (PyOS_strnicmp (p , "replace" , 7 ) == 0 );
98
- break ;
99
82
}
100
83
101
84
pysqlite_Statement * self = PyObject_GC_New (pysqlite_Statement ,
@@ -139,73 +122,61 @@ stmt_traverse(pysqlite_Statement *self, visitproc visit, void *arg)
139
122
}
140
123
141
124
/*
142
- * Checks if there is anything left in an SQL string after SQLite compiled it.
143
- * This is used to check if somebody tried to execute more than one SQL command
144
- * with one execute()/executemany() command, which the DB-API and we don't
145
- * allow.
125
+ * Strip leading whitespace and comments from incoming SQL (null terminated C
126
+ * string) and return a pointer to the first non-whitespace, non-comment
127
+ * character.
146
128
*
147
- * Returns 1 if there is more left than should be. 0 if ok.
129
+ * This is used to check if somebody tries to execute more than one SQL query
130
+ * with one execute()/executemany() command, which the DB-API don't allow.
131
+ *
132
+ * It is also used to harden DML query detection.
148
133
*/
149
- static int pysqlite_check_remaining_sql (const char * tail )
134
+ static inline const char *
135
+ lstrip_sql (const char * sql )
150
136
{
151
- const char * pos = tail ;
152
-
153
- parse_remaining_sql_state state = NORMAL ;
154
-
155
- for (;;) {
137
+ // This loop is borrowed from the SQLite source code.
138
+ for (const char * pos = sql ; * pos ; pos ++ ) {
156
139
switch (* pos ) {
157
- case 0 :
158
- return 0 ;
159
- case '-' :
160
- if (state == NORMAL ) {
161
- state = LINECOMMENT_1 ;
162
- } else if (state == LINECOMMENT_1 ) {
163
- state = IN_LINECOMMENT ;
164
- }
165
- break ;
166
140
case ' ' :
167
141
case '\t' :
168
- break ;
142
+ case '\f' :
169
143
case '\n' :
170
- case 13 :
171
- if (state == IN_LINECOMMENT ) {
172
- state = NORMAL ;
173
- }
144
+ case '\r' :
145
+ // Skip whitespace.
174
146
break ;
175
- case '/' :
176
- if (state == NORMAL ) {
177
- state = COMMENTSTART_1 ;
178
- } else if (state == COMMENTEND_1 ) {
179
- state = NORMAL ;
180
- } else if (state == COMMENTSTART_1 ) {
181
- return 1 ;
147
+ case '-' :
148
+ // Skip line comments.
149
+ if (pos [1 ] == '-' ) {
150
+ pos += 2 ;
151
+ while (pos [0 ] && pos [0 ] != '\n' ) {
152
+ pos ++ ;
153
+ }
154
+ if (pos [0 ] == '\0' ) {
155
+ return NULL ;
156
+ }
157
+ continue ;
182
158
}
183
- break ;
184
- case '*' :
185
- if (state == NORMAL ) {
186
- return 1 ;
187
- } else if (state == LINECOMMENT_1 ) {
188
- return 1 ;
189
- } else if (state == COMMENTSTART_1 ) {
190
- state = IN_COMMENT ;
191
- } else if (state == IN_COMMENT ) {
192
- state = COMMENTEND_1 ;
159
+ return pos ;
160
+ case '/' :
161
+ // Skip C style comments.
162
+ if (pos [1 ] == '*' ) {
163
+ pos += 2 ;
164
+ while (pos [0 ] && (pos [0 ] != '*' || pos [1 ] != '/' )) {
165
+ pos ++ ;
166
+ }
167
+ if (pos [0 ] == '\0' ) {
168
+ return NULL ;
169
+ }
170
+ pos ++ ;
171
+ continue ;
193
172
}
194
- break ;
173
+ return pos ;
195
174
default :
196
- if (state == COMMENTEND_1 ) {
197
- state = IN_COMMENT ;
198
- } else if (state == IN_LINECOMMENT ) {
199
- } else if (state == IN_COMMENT ) {
200
- } else {
201
- return 1 ;
202
- }
175
+ return pos ;
203
176
}
204
-
205
- pos ++ ;
206
177
}
207
178
208
- return 0 ;
179
+ return NULL ;
209
180
}
210
181
211
182
static PyType_Slot stmt_slots [] = {
0 commit comments