Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * psql - the PostgreSQL interactive terminal
3 : : *
4 : : * Copyright (c) 2000-2024, PostgreSQL Global Development Group
5 : : *
6 : : * src/bin/psql/stringutils.c
7 : : */
8 : : #include "postgres_fe.h"
9 : :
10 : : #include <ctype.h>
11 : :
12 : : #include "common.h"
13 : : #include "stringutils.h"
14 : :
15 : :
16 : : /*
17 : : * Replacement for strtok() (a.k.a. poor man's flex)
18 : : *
19 : : * Splits a string into tokens, returning one token per call, then NULL
20 : : * when no more tokens exist in the given string.
21 : : *
22 : : * The calling convention is similar to that of strtok, but with more
23 : : * frammishes.
24 : : *
25 : : * s - string to parse, if NULL continue parsing the last string
26 : : * whitespace - set of whitespace characters that separate tokens
27 : : * delim - set of non-whitespace separator characters (or NULL)
28 : : * quote - set of characters that can quote a token (NULL if none)
29 : : * escape - character that can quote quotes (0 if none)
30 : : * e_strings - if true, treat E'...' syntax as a valid token
31 : : * del_quotes - if true, strip quotes from the returned token, else return
32 : : * it exactly as found in the string
33 : : * encoding - the active character-set encoding
34 : : *
35 : : * Characters in 'delim', if any, will be returned as single-character
36 : : * tokens unless part of a quoted token.
37 : : *
38 : : * Double occurrences of the quoting character are always taken to represent
39 : : * a single quote character in the data. If escape isn't 0, then escape
40 : : * followed by anything (except \0) is a data character too.
41 : : *
42 : : * The combination of e_strings and del_quotes both true is not currently
43 : : * handled. This could be fixed but it's not needed anywhere at the moment.
44 : : *
45 : : * Note that the string s is _not_ overwritten in this implementation.
46 : : *
47 : : * NB: it's okay to vary delim, quote, and escape from one call to the
48 : : * next on a single source string, but changing whitespace is a bad idea
49 : : * since you might lose data.
50 : : */
51 : : char *
8928 bruce@momjian.us 52 :CBC 507 : strtokx(const char *s,
53 : : const char *whitespace,
54 : : const char *delim,
55 : : const char *quote,
56 : : char escape,
57 : : bool e_strings,
58 : : bool del_quotes,
59 : : int encoding)
60 : : {
61 : : static char *storage = NULL; /* store the local copy of the users
62 : : * string here */
63 : : static char *string = NULL; /* pointer into storage where to continue on
64 : : * next call */
65 : :
66 : : /* variously abused variables: */
67 : : unsigned int offset;
68 : : char *start;
69 : : char *p;
70 : :
71 [ + + ]: 507 : if (s)
72 : : {
73 : 93 : free(storage);
74 : :
75 : : /*
76 : : * We may need extra space to insert delimiter nulls for adjacent
77 : : * tokens. 2X the space is a gross overestimate, but it's unlikely
78 : : * that this code will be used on huge strings anyway.
79 : : */
7385 neilc@samurai.com 80 : 93 : storage = pg_malloc(2 * strlen(s) + 1);
7848 tgl@sss.pgh.pa.us 81 : 93 : strcpy(storage, s);
8928 bruce@momjian.us 82 : 93 : string = storage;
83 : : }
84 : :
85 [ - + ]: 507 : if (!storage)
8928 bruce@momjian.us 86 :UBC 0 : return NULL;
87 : :
88 : : /* skip leading whitespace */
7848 tgl@sss.pgh.pa.us 89 :CBC 507 : offset = strspn(string, whitespace);
90 : 507 : start = &string[offset];
91 : :
92 : : /* end of string reached? */
93 [ + + ]: 507 : if (*start == '\0')
94 : : {
95 : : /* technically we don't need to free here, but we're nice */
8928 bruce@momjian.us 96 : 56 : free(storage);
97 : 56 : storage = NULL;
98 : 56 : string = NULL;
99 : 56 : return NULL;
100 : : }
101 : :
102 : : /* test if delimiter character */
7848 tgl@sss.pgh.pa.us 103 [ + + + + ]: 451 : if (delim && strchr(delim, *start))
104 : : {
105 : : /*
106 : : * If not at end of string, we need to insert a null to terminate the
107 : : * returned token. We can just overwrite the next character if it
108 : : * happens to be in the whitespace set ... otherwise move over the
109 : : * rest of the string to make room. (This is why we allocated extra
110 : : * space above).
111 : : */
112 : 42 : p = start + 1;
113 [ + - ]: 42 : if (*p != '\0')
114 : : {
115 [ + + ]: 42 : if (!strchr(whitespace, *p))
116 : 24 : memmove(p + 1, p, strlen(p) + 1);
117 : 42 : *p = '\0';
118 : 42 : string = p + 1;
119 : : }
120 : : else
121 : : {
122 : : /* at end of string, so no extra work */
7848 tgl@sss.pgh.pa.us 123 :UBC 0 : string = p;
124 : : }
125 : :
7848 tgl@sss.pgh.pa.us 126 :CBC 42 : return start;
127 : : }
128 : :
129 : : /* check for E string */
6527 130 : 409 : p = start;
131 [ + + ]: 409 : if (e_strings &&
132 [ + - - + ]: 141 : (*p == 'E' || *p == 'e') &&
6527 tgl@sss.pgh.pa.us 133 [ # # ]:UBC 0 : p[1] == '\'')
134 : : {
135 : 0 : quote = "'";
136 : 0 : escape = '\\'; /* if std strings before, not any more */
137 : 0 : p++;
138 : : }
139 : :
140 : : /* test if quoting character */
6527 tgl@sss.pgh.pa.us 141 [ + + + + ]:CBC 409 : if (quote && strchr(quote, *p))
142 : : {
143 : : /* okay, we have a quoted token, now scan for the closer */
144 : 78 : char thisquote = *p++;
145 : :
1042 146 [ + + ]: 1331 : for (; *p; p += PQmblenBounded(p, encoding))
147 : : {
7848 148 [ - + - - ]: 1325 : if (*p == escape && p[1] != '\0')
7848 tgl@sss.pgh.pa.us 149 :UBC 0 : p++; /* process escaped anything */
7848 tgl@sss.pgh.pa.us 150 [ + + - + ]:CBC 1325 : else if (*p == thisquote && p[1] == thisquote)
7848 tgl@sss.pgh.pa.us 151 :UBC 0 : p++; /* process doubled quote */
7848 tgl@sss.pgh.pa.us 152 [ + + ]:CBC 1325 : else if (*p == thisquote)
153 : : {
154 : 72 : p++; /* skip trailing quote */
155 : 72 : break;
156 : : }
157 : : }
158 : :
159 : : /*
160 : : * If not at end of string, we need to insert a null to terminate the
161 : : * returned token. See notes above.
162 : : */
8928 bruce@momjian.us 163 [ + + ]: 78 : if (*p != '\0')
164 : : {
7848 tgl@sss.pgh.pa.us 165 [ + + ]: 30 : if (!strchr(whitespace, *p))
166 : 18 : memmove(p + 1, p, strlen(p) + 1);
8928 bruce@momjian.us 167 : 30 : *p = '\0';
168 : 30 : string = p + 1;
169 : : }
170 : : else
171 : : {
172 : : /* at end of string, so no extra work */
173 : 48 : string = p;
174 : : }
175 : :
176 : : /* Clean up the token if caller wants that */
7848 tgl@sss.pgh.pa.us 177 [ + + ]: 78 : if (del_quotes)
178 : 6 : strip_quotes(start, thisquote, escape, encoding);
179 : :
180 : 78 : return start;
181 : : }
182 : :
183 : : /*
184 : : * Otherwise no quoting character. Scan till next whitespace, delimiter
185 : : * or quote. NB: at this point, *start is known not to be '\0',
186 : : * whitespace, delim, or quote, so we will consume at least one character.
187 : : */
188 : 331 : offset = strcspn(start, whitespace);
189 : :
190 [ + + ]: 331 : if (delim)
191 : : {
192 : 300 : unsigned int offset2 = strcspn(start, delim);
193 : :
194 [ + + ]: 300 : if (offset > offset2)
195 : 36 : offset = offset2;
196 : : }
197 : :
198 [ + + ]: 331 : if (quote)
199 : : {
200 : 306 : unsigned int offset2 = strcspn(start, quote);
201 : :
202 [ + + ]: 306 : if (offset > offset2)
203 : 12 : offset = offset2;
204 : : }
205 : :
206 : 331 : p = start + offset;
207 : :
208 : : /*
209 : : * If not at end of string, we need to insert a null to terminate the
210 : : * returned token. See notes above.
211 : : */
212 [ + + ]: 331 : if (*p != '\0')
213 : : {
214 [ + + ]: 286 : if (!strchr(whitespace, *p))
215 : 42 : memmove(p + 1, p, strlen(p) + 1);
216 : 286 : *p = '\0';
217 : 286 : string = p + 1;
218 : : }
219 : : else
220 : : {
221 : : /* at end of string, so no extra work */
222 : 45 : string = p;
223 : : }
224 : :
225 : 331 : return start;
226 : : }
227 : :
228 : :
229 : : /*
230 : : * strip_quotes
231 : : *
232 : : * Remove quotes from the string at *source. Leading and trailing occurrences
233 : : * of 'quote' are removed; embedded double occurrences of 'quote' are reduced
234 : : * to single occurrences; if 'escape' is not 0 then 'escape' removes special
235 : : * significance of next character.
236 : : *
237 : : * Note that the source string is overwritten in-place.
238 : : */
239 : : void
240 : 52 : strip_quotes(char *source, char quote, char escape, int encoding)
241 : : {
242 : : char *src;
243 : : char *dst;
244 : :
4139 andrew@dunslane.net 245 [ - + ]: 52 : Assert(source != NULL);
246 [ - + ]: 52 : Assert(quote != '\0');
247 : :
7848 tgl@sss.pgh.pa.us 248 : 52 : src = dst = source;
249 : :
250 [ + - + + ]: 52 : if (*src && *src == quote)
251 : 51 : src++; /* skip leading quote */
252 : :
253 [ + + ]: 1355 : while (*src)
254 : : {
255 : 1348 : char c = *src;
256 : : int i;
257 : :
258 [ + + - + ]: 1348 : if (c == quote && src[1] == '\0')
259 : : break; /* skip trailing quote */
260 [ - + - - ]: 1303 : else if (c == quote && src[1] == quote)
7848 tgl@sss.pgh.pa.us 261 :UBC 0 : src++; /* process doubled quote */
7848 tgl@sss.pgh.pa.us 262 [ - + - - ]:CBC 1303 : else if (c == escape && src[1] != '\0')
7848 tgl@sss.pgh.pa.us 263 :UBC 0 : src++; /* process escaped character */
264 : :
1042 tgl@sss.pgh.pa.us 265 :CBC 1303 : i = PQmblenBounded(src, encoding);
7848 266 [ + + ]: 2606 : while (i--)
267 : 1303 : *dst++ = *src++;
268 : : }
269 : :
270 : 52 : *dst = '\0';
8928 bruce@momjian.us 271 : 52 : }
272 : :
273 : :
274 : : /*
275 : : * quote_if_needed
276 : : *
277 : : * Opposite of strip_quotes(). If "source" denotes itself literally without
278 : : * quoting or escaping, returns NULL. Otherwise, returns a malloc'd copy with
279 : : * quoting and escaping applied:
280 : : *
281 : : * source - string to parse
282 : : * entails_quote - any of these present? need outer quotes
283 : : * quote - doubled within string, affixed to both ends
284 : : * escape - doubled within string
285 : : * force_quote - if true, quote the output even if it doesn't "need" it
286 : : * encoding - the active character-set encoding
287 : : *
288 : : * Do not use this as a substitute for PQescapeStringConn(). Use it for
289 : : * strings to be parsed by strtokx() or psql_scan_slash_option().
290 : : */
291 : : char *
4429 alvherre@alvh.no-ip. 292 : 5 : quote_if_needed(const char *source, const char *entails_quote,
293 : : char quote, char escape, bool force_quote,
294 : : int encoding)
295 : : {
296 : : const char *src;
297 : : char *ret;
298 : : char *dst;
1543 tgl@sss.pgh.pa.us 299 : 5 : bool need_quotes = force_quote;
300 : :
4139 andrew@dunslane.net 301 [ - + ]: 5 : Assert(source != NULL);
302 [ - + ]: 5 : Assert(quote != '\0');
303 : :
4429 alvherre@alvh.no-ip. 304 : 5 : src = source;
4326 bruce@momjian.us 305 : 5 : dst = ret = pg_malloc(2 * strlen(src) + 3); /* excess */
306 : :
4429 alvherre@alvh.no-ip. 307 : 5 : *dst++ = quote;
308 : :
309 [ + + ]: 101 : while (*src)
310 : : {
311 : 96 : char c = *src;
312 : : int i;
313 : :
314 [ - + ]: 96 : if (c == quote)
315 : : {
4429 alvherre@alvh.no-ip. 316 :UBC 0 : need_quotes = true;
317 : 0 : *dst++ = quote;
318 : : }
4429 alvherre@alvh.no-ip. 319 [ - + ]:CBC 96 : else if (c == escape)
320 : : {
4429 alvherre@alvh.no-ip. 321 :UBC 0 : need_quotes = true;
322 : 0 : *dst++ = escape;
323 : : }
4429 alvherre@alvh.no-ip. 324 [ - + ]:CBC 96 : else if (strchr(entails_quote, c))
4429 alvherre@alvh.no-ip. 325 :UBC 0 : need_quotes = true;
326 : :
1042 tgl@sss.pgh.pa.us 327 :CBC 96 : i = PQmblenBounded(src, encoding);
4429 alvherre@alvh.no-ip. 328 [ + + ]: 192 : while (i--)
329 : 96 : *dst++ = *src++;
330 : : }
331 : :
332 : 5 : *dst++ = quote;
333 : 5 : *dst = '\0';
334 : :
335 [ + + ]: 5 : if (!need_quotes)
336 : : {
337 : 2 : free(ret);
338 : 2 : ret = NULL;
339 : : }
340 : :
341 : 5 : return ret;
342 : : }
|