1 /*
2  * Some utility functions for Postgres.
3  *
4  * - Literal & ident quoting.
5  * - Array parsing
6  */
7 
8 #include <usual/pgutil.h>
9 
10 #include <usual/ctype.h>
11 
12 /* str -> E'str' */
pg_quote_literal(char * _dst,const char * _src,int dstlen)13 bool pg_quote_literal(char *_dst, const char *_src, int dstlen)
14 {
15 	char *dst = _dst;
16 	char *end = _dst + dstlen - 2;
17 	const char *src = _src;
18 	bool stdquote = true;
19 
20 	if (dstlen < 3)
21 		return false;
22 
23 	if (_src == NULL) {
24 		if (dstlen < 5)
25 			return false;
26 		memcpy(_dst, "NULL", 5);
27 		return true;
28 	}
29 
30 retry:
31 	*dst++ = '\'';
32 	while (*src && dst < end) {
33 		if (*src == '\'')
34 			*dst++ = '\'';
35 		else if (*src == '\\') {
36 			if (stdquote)
37 				goto retry_ext;
38 			*dst++ = '\\';
39 		}
40 		*dst++ = *src++;
41 	}
42 	if (*src || dst > end)
43 		return false;
44 
45 	*dst++ = '\'';
46 	*dst = 0;
47 
48 	return true;
49 retry_ext:
50 	/* string contains '\\', retry as E'' string */
51 	dst = _dst;
52 	src = _src;
53 	*dst++ = 'E';
54 	stdquote = false;
55 	goto retry;
56 }
57 
id_start(unsigned char c)58 static inline bool id_start(unsigned char c)
59 {
60 	return (c >= 'a' && c <= 'z') || c == '_';
61 }
62 
id_body(unsigned char c)63 static inline bool id_body(unsigned char c)
64 {
65 	return id_start(c) || (c >= '0' && c <= '9');
66 }
67 
68 /* ident -> "ident" */
pg_quote_ident(char * _dst,const char * _src,int dstlen)69 bool pg_quote_ident(char *_dst, const char *_src, int dstlen)
70 {
71 	char *dst = _dst;
72 	char *end = _dst + dstlen - 1;
73 	const char *src = _src;
74 
75 	if (dstlen < 1)
76 		return false;
77 
78 	if (!id_start(*src))
79 		goto needs_quoting;
80 
81 	while (*src && dst < end) {
82 		if (!id_body(*src))
83 			goto needs_quoting;
84 		*dst++ = *src++;
85 	}
86 	if (*src)
87 		return false;
88 	*dst = 0;
89 
90 	if (!pg_is_reserved_word(_dst))
91 		return true;
92 
93 needs_quoting:
94 	dst = _dst;
95 	src = _src;
96 	end = _dst + dstlen - 2;
97 	if (dstlen < 3)
98 		return false;
99 	*dst++ = '"';
100 	while (*src && dst < end) {
101 		if (*src == '"')
102 			*dst++ = *src;
103 		*dst++ = *src++;
104 	}
105 	if (*src)
106 		return false;
107 	*dst++ = '"';
108 	*dst = 0;
109 	return true;
110 }
111 
112 /* schema.name -> "schema"."name" */
pg_quote_fqident(char * _dst,const char * _src,int dstlen)113 bool pg_quote_fqident(char *_dst, const char *_src, int dstlen)
114 {
115 	const char *dot = strchr(_src, '.');
116 	char scmbuf[128];
117 	const char *scm;
118 	int scmlen;
119 	if (dot) {
120 		scmlen = dot - _src;
121 		if (scmlen >= (int)sizeof(scmbuf))
122 			return false;
123 		memcpy(scmbuf, _src, scmlen);
124 		scmbuf[scmlen] = 0;
125 		scm = scmbuf;
126 		_src = dot + 1;
127 	} else {
128 		scm = "public";
129 	}
130 	if (!pg_quote_ident(_dst, scm, dstlen))
131 		return false;
132 
133 	scmlen = strlen(_dst);
134 	_dst[scmlen] = '.';
135 	_dst += scmlen + 1;
136 	dstlen -= scmlen + 1;
137 	if (!pg_quote_ident(_dst, _src, dstlen))
138 		return false;
139 	return true;
140 }
141 
142 /*
143  * pgarray parsing
144  */
145 
parse_value(struct StrList * arr,const char * val,const char * vend,CxMem * cx)146 static bool parse_value(struct StrList *arr, const char *val, const char *vend,
147 			CxMem *cx)
148 {
149 	int len;
150 	const char *s;
151 	char *str, *p;
152 	unsigned c;
153 
154 	while (val < vend && isspace(*val))
155 		val++;
156 	while (vend > val && isspace(vend[-1]))
157 		vend--;
158 	if (val == vend) return false;
159 
160 	s = val;
161 	len = vend - val;
162 	if (len == 4 && !strncasecmp(val, "null", len)) {
163 		return strlist_append_ref(arr, NULL);
164 	}
165 	p = str = cx_alloc(cx, len + 1);
166 	if (!str)
167 		return false;
168 
169 	/* unquote & copy */
170 	while (s < vend) {
171 		c = *s++;
172 		if (c == '"') {
173 			while (1) {
174 				c = *s++;
175 				if (c == '"')
176 					break;
177 				else if (c == '\\')
178 					*p++ = *s++;
179 				else
180 					*p++ = c;
181 			}
182 		} else if (c == '\\') {
183 			*p++ = *s++;
184 		} else
185 			*p++ = c;
186 	}
187 	*p++ = 0;
188 	if (!strlist_append_ref(arr, str)) {
189 		cx_free(cx, str);
190 		return false;
191 	}
192 	return true;
193 }
194 
pg_parse_array(const char * pgarr,CxMem * cx)195 struct StrList *pg_parse_array(const char *pgarr, CxMem *cx)
196 {
197 	const char *s = pgarr;
198 	struct StrList *lst;
199 	const char *val = NULL;
200 	unsigned c;
201 
202 	/* skip dimension def "[x,y]={..}" */
203 	if (*s == '[') {
204 		s = strchr(s, ']');
205 		if (!s || s[1] != '=')
206 			return NULL;
207 		s += 2;
208 	}
209 	if (*s++ != '{')
210 		return NULL;
211 
212 	lst = strlist_new(cx);
213 	if (!lst)
214 		return NULL;
215 
216 	while (*s) {
217 		/* array end */
218 		if (s[0] == '}') {
219 			if (s[1] != 0) {
220 				goto failed;
221 			}
222 			if (val) {
223 				if (!parse_value(lst, val, s, cx))
224 					goto failed;
225 			}
226 			return lst;
227 		}
228 
229 		/* cannot init earlier to support empty arrays */
230 		if (!val)
231 			val = s;
232 
233 		/* val done? */
234 		if (*s == ',') {
235 			if (!parse_value(lst, val, s, cx))
236 				goto failed;
237 			val = ++s;
238 			continue;
239 		}
240 
241 		/* scan value */
242 		c = *s++;
243 		if (c == '"') {
244 			while (1) {
245 				c = *s++;
246 				if (c == '"')
247 					break;
248 				else if (c == '\\') {
249 					if (!*s) goto failed;
250 					s++;
251 				} else if (!*s)
252 					goto failed;
253 			}
254 		} else if (c == '\\') {
255 			if (!*s) goto failed;
256 			s++;
257 		}
258 	}
259 	if (s[-1] != '}')
260 		goto failed;
261 	return lst;
262 failed:
263 	strlist_free(lst);
264 	return NULL;
265 }
266 
267 /*
268  * Postgres keyword lookup.
269  */
270 
271 /* gperf tries ot inline a non-static function. */
272 #undef inline
273 #undef __inline
274 #undef __attribute__
275 #define inline
276 #define __inline
277 #define __attribute__(x)
278 #define long uintptr_t
279 
280 /* include gperf code */
281 const char *pg_keyword_lookup_real(const char *str, unsigned int len);
282 #include <usual/pgutil_kwlookup.h>
283 
pg_is_reserved_word(const char * str)284 bool pg_is_reserved_word(const char *str)
285 {
286 	const char *kw = pg_keyword_lookup_real(str, strlen(str));
287 	return kw != NULL;
288 }
289