Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Utility routines for SQL dumping
4 : : *
5 : : * Basically this is stuff that is useful in both pg_dump and pg_dumpall.
6 : : *
7 : : *
8 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
9 : : * Portions Copyright (c) 1994, Regents of the University of California
10 : : *
11 : : * src/bin/pg_dump/dumputils.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres_fe.h"
16 : :
17 : : #include <ctype.h>
18 : :
19 : : #include "common/file_perm.h"
20 : : #include "common/logging.h"
21 : : #include "dumputils.h"
22 : : #include "fe_utils/string_utils.h"
23 : :
24 : : static const char restrict_chars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
25 : :
26 : : static bool parseAclItem(const char *item, const char *type,
27 : : const char *name, const char *subname, int remoteVersion,
28 : : PQExpBuffer grantee, PQExpBuffer grantor,
29 : : PQExpBuffer privs, PQExpBuffer privswgo);
30 : : static char *dequoteAclUserName(PQExpBuffer output, char *input);
31 : : static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
32 : : const char *subname);
33 : :
34 : :
35 : : /*
36 : : * Sanitize a string to be included in an SQL comment or TOC listing, by
37 : : * replacing any newlines with spaces. This ensures each logical output line
38 : : * is in fact one physical output line, to prevent corruption of the dump
39 : : * (which could, in the worst case, present an SQL injection vulnerability
40 : : * if someone were to incautiously load a dump containing objects with
41 : : * maliciously crafted names).
42 : : *
43 : : * The result is a freshly malloc'd string. If the input string is NULL,
44 : : * return a malloc'ed empty string, unless want_hyphen, in which case return a
45 : : * malloc'ed hyphen.
46 : : *
47 : : * Note that we currently don't bother to quote names, meaning that the name
48 : : * fields aren't automatically parseable. "pg_restore -L" doesn't care because
49 : : * it only examines the dumpId field, but someday we might want to try harder.
50 : : */
51 : : char *
26 noah@leadboat.com 52 :CBC 129389 : sanitize_line(const char *str, bool want_hyphen)
53 : : {
54 : : char *result;
55 : : char *s;
56 : :
57 [ + + ]: 129389 : if (!str)
58 [ + + ]: 5405 : return pg_strdup(want_hyphen ? "-" : "");
59 : :
60 : 123984 : result = pg_strdup(str);
61 : :
62 [ + + ]: 1515520 : for (s = result; *s != '\0'; s++)
63 : : {
64 [ + + + + ]: 1391536 : if (*s == '\n' || *s == '\r')
65 : 228 : *s = ' ';
66 : : }
67 : :
68 : 123984 : return result;
69 : : }
70 : :
71 : :
72 : : /*
73 : : * Build GRANT/REVOKE command(s) for an object.
74 : : *
75 : : * name: the object name, in the form to use in the commands (already quoted)
76 : : * subname: the sub-object name, if any (already quoted); NULL if none
77 : : * nspname: the namespace the object is in (NULL if none); not pre-quoted
78 : : * type: the object type (as seen in GRANT command: must be one of
79 : : * TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
80 : : * FOREIGN DATA WRAPPER, SERVER, PARAMETER or LARGE OBJECT)
81 : : * acls: the ACL string fetched from the database
82 : : * baseacls: the initial ACL string for this object
83 : : * owner: username of object owner (will be passed through fmtId); can be
84 : : * NULL or empty string to indicate "no owner known"
85 : : * prefix: string to prefix to each generated command; typically empty
86 : : * remoteVersion: version of database
87 : : *
88 : : * Returns true if okay, false if could not parse the acl string.
89 : : * The resulting commands (if any) are appended to the contents of 'sql'.
90 : : *
91 : : * baseacls is typically the result of acldefault() for the object's type
92 : : * and owner. However, if there is a pg_init_privs entry for the object,
93 : : * it should instead be the initprivs ACLs. When acls is itself a
94 : : * pg_init_privs entry, baseacls is what to dump that relative to; then
95 : : * it can be either an acldefault() value or an empty ACL "{}".
96 : : *
97 : : * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES "
98 : : * or something similar, and name is an empty string.
99 : : *
100 : : * Note: beware of passing a fmtId() result directly as 'name' or 'subname',
101 : : * since this routine uses fmtId() internally.
102 : : */
103 : : bool
2749 tgl@sss.pgh.pa.us 104 : 28507 : buildACLCommands(const char *name, const char *subname, const char *nspname,
105 : : const char *type, const char *acls, const char *baseacls,
106 : : const char *owner, const char *prefix, int remoteVersion,
107 : : PQExpBuffer sql)
108 : : {
3860 109 : 28507 : bool ok = true;
3440 sfrost@snowman.net 110 : 28507 : char **aclitems = NULL;
1370 tgl@sss.pgh.pa.us 111 : 28507 : char **baseitems = NULL;
112 : 28507 : char **grantitems = NULL;
113 : 28507 : char **revokeitems = NULL;
3440 sfrost@snowman.net 114 : 28507 : int naclitems = 0;
1370 tgl@sss.pgh.pa.us 115 : 28507 : int nbaseitems = 0;
116 : 28507 : int ngrantitems = 0;
117 : 28507 : int nrevokeitems = 0;
118 : : int i;
119 : : PQExpBuffer grantee,
120 : : grantor,
121 : : privs,
122 : : privswgo;
123 : : PQExpBuffer firstsql,
124 : : secondsql;
125 : :
126 : : /*
127 : : * If the acl was NULL (initial default state), we need do nothing. Note
128 : : * that this is distinguishable from all-privileges-revoked, which will
129 : : * look like an empty array ("{}").
130 : : */
131 [ + - + + ]: 28507 : if (acls == NULL || *acls == '\0')
8135 132 : 117 : return true; /* object has default permissions */
133 : :
134 : : /* treat empty-string owner same as NULL */
7217 135 [ + - - + ]: 28390 : if (owner && *owner == '\0')
7217 tgl@sss.pgh.pa.us 136 :UBC 0 : owner = NULL;
137 : :
138 : : /* Parse the acls array */
1370 tgl@sss.pgh.pa.us 139 [ - + ]:CBC 28390 : if (!parsePGArray(acls, &aclitems, &naclitems))
140 : : {
1178 peter@eisentraut.org 141 :UBC 0 : free(aclitems);
1370 tgl@sss.pgh.pa.us 142 : 0 : return false;
143 : : }
144 : :
145 : : /* Parse the baseacls too */
1360 tgl@sss.pgh.pa.us 146 [ - + ]:CBC 28390 : if (!parsePGArray(baseacls, &baseitems, &nbaseitems))
147 : : {
1178 peter@eisentraut.org 148 :UBC 0 : free(aclitems);
149 : 0 : free(baseitems);
1360 tgl@sss.pgh.pa.us 150 : 0 : return false;
151 : : }
152 : :
153 : : /*
154 : : * Compare the actual ACL with the base ACL, extracting the privileges
155 : : * that need to be granted (i.e., are in the actual ACL but not the base
156 : : * ACL) and the ones that need to be revoked (the reverse). We use plain
157 : : * string comparisons to check for matches. In principle that could be
158 : : * fooled by extraneous issues such as whitespace, but since all these
159 : : * strings are the work of aclitemout(), it should be OK in practice.
160 : : * Besides, a false mismatch will just cause the output to be a little
161 : : * more verbose than it really needed to be.
162 : : */
1370 tgl@sss.pgh.pa.us 163 :CBC 28390 : grantitems = (char **) pg_malloc(naclitems * sizeof(char *));
164 [ + + ]: 78712 : for (i = 0; i < naclitems; i++)
165 : : {
166 : 50322 : bool found = false;
167 : :
168 [ + + ]: 72806 : for (int j = 0; j < nbaseitems; j++)
169 : : {
170 [ + + ]: 70801 : if (strcmp(aclitems[i], baseitems[j]) == 0)
171 : : {
172 : 48317 : found = true;
173 : 48317 : break;
174 : : }
175 : : }
176 [ + + ]: 50322 : if (!found)
177 : 2005 : grantitems[ngrantitems++] = aclitems[i];
178 : : }
179 : 28390 : revokeitems = (char **) pg_malloc(nbaseitems * sizeof(char *));
180 [ + + ]: 77103 : for (i = 0; i < nbaseitems; i++)
181 : : {
182 : 48713 : bool found = false;
183 : :
184 [ + + ]: 70476 : for (int j = 0; j < naclitems; j++)
185 : : {
186 [ + + ]: 70080 : if (strcmp(baseitems[i], aclitems[j]) == 0)
187 : : {
188 : 48317 : found = true;
189 : 48317 : break;
190 : : }
191 : : }
192 [ + + ]: 48713 : if (!found)
193 : 396 : revokeitems[nrevokeitems++] = baseitems[i];
194 : : }
195 : :
196 : : /* Prepare working buffers */
8135 197 : 28390 : grantee = createPQExpBuffer();
198 : 28390 : grantor = createPQExpBuffer();
199 : 28390 : privs = createPQExpBuffer();
200 : 28390 : privswgo = createPQExpBuffer();
201 : :
202 : : /*
203 : : * At the end, these two will be pasted together to form the result.
204 : : */
8080 peter_e@gmx.net 205 : 28390 : firstsql = createPQExpBuffer();
206 : 28390 : secondsql = createPQExpBuffer();
207 : :
208 : : /*
209 : : * Build REVOKE statements for ACLs listed in revokeitems[].
210 : : */
1357 tgl@sss.pgh.pa.us 211 [ + + ]: 28786 : for (i = 0; i < nrevokeitems; i++)
212 : : {
213 [ - + ]: 396 : if (!parseAclItem(revokeitems[i],
214 : : type, name, subname, remoteVersion,
215 : : grantee, grantor, privs, NULL))
216 : : {
1357 tgl@sss.pgh.pa.us 217 :UBC 0 : ok = false;
218 : 0 : break;
219 : : }
220 : :
1357 tgl@sss.pgh.pa.us 221 [ + - ]:CBC 396 : if (privs->len > 0)
222 : : {
223 : 396 : appendPQExpBuffer(firstsql, "%sREVOKE %s ON %s ",
224 : : prefix, privs->data, type);
225 [ + + + - ]: 396 : if (nspname && *nspname)
226 : 218 : appendPQExpBuffer(firstsql, "%s.", fmtId(nspname));
999 jdavis@postgresql.or 227 [ + - + + ]: 396 : if (name && *name)
228 : 314 : appendPQExpBuffer(firstsql, "%s ", name);
229 : 396 : appendPQExpBufferStr(firstsql, "FROM ");
1357 tgl@sss.pgh.pa.us 230 [ + + ]: 396 : if (grantee->len == 0)
231 : 204 : appendPQExpBufferStr(firstsql, "PUBLIC;\n");
232 : : else
233 : 192 : appendPQExpBuffer(firstsql, "%s;\n",
234 : 192 : fmtId(grantee->data));
235 : : }
236 : : }
237 : :
238 : : /*
239 : : * At this point we have issued REVOKE statements for all initial and
240 : : * default privileges that are no longer present on the object, so we are
241 : : * almost ready to GRANT the privileges listed in grantitems[].
242 : : *
243 : : * We still need some hacking though to cover the case where new default
244 : : * public privileges are added in new versions: the REVOKE ALL will revoke
245 : : * them, leading to behavior different from what the old version had,
246 : : * which is generally not what's wanted. So add back default privs if the
247 : : * source database is too old to have had that particular priv. (As of
248 : : * right now, no such cases exist in supported versions.)
249 : : */
250 : :
251 : : /*
252 : : * Scan individual ACL items to be granted.
253 : : *
254 : : * The order in which privileges appear in the ACL string (the order they
255 : : * have been GRANT'd in, which the backend maintains) must be preserved to
256 : : * ensure that GRANTs WITH GRANT OPTION and subsequent GRANTs based on
257 : : * those are dumped in the correct order. However, some old server
258 : : * versions will show grants to PUBLIC before the owner's own grants; for
259 : : * consistency's sake, force the owner's grants to be output first.
260 : : */
1370 261 [ + + ]: 30395 : for (i = 0; i < ngrantitems; i++)
262 : : {
263 [ + - ]: 2005 : if (parseAclItem(grantitems[i], type, name, subname, remoteVersion,
264 : : grantee, grantor, privs, privswgo))
265 : : {
266 : : /*
267 : : * If the grantor isn't the owner, we'll need to use SET SESSION
268 : : * AUTHORIZATION to become the grantor. Issue the SET/RESET only
269 : : * if there's something useful to do.
270 : : */
271 [ + + + - ]: 2005 : if (privs->len > 0 || privswgo->len > 0)
272 : : {
273 : : PQExpBuffer thissql;
274 : :
275 : : /* Set owner as grantor if that's not explicit in the ACL */
276 [ - + - - ]: 2005 : if (grantor->len == 0 && owner)
1370 tgl@sss.pgh.pa.us 277 :UBC 0 : printfPQExpBuffer(grantor, "%s", owner);
278 : :
279 : : /* Make sure owner's own grants are output before others */
1370 tgl@sss.pgh.pa.us 280 [ + - ]:CBC 2005 : if (owner &&
281 [ + + ]: 2005 : strcmp(grantee->data, owner) == 0 &&
282 [ + - ]: 136 : strcmp(grantor->data, owner) == 0)
283 : 136 : thissql = firstsql;
284 : : else
285 : 1869 : thissql = secondsql;
286 : :
8080 peter_e@gmx.net 287 [ + - ]: 2005 : if (grantor->len > 0
288 [ + - - + ]: 2005 : && (!owner || strcmp(owner, grantor->data) != 0))
1370 tgl@sss.pgh.pa.us 289 :UBC 0 : appendPQExpBuffer(thissql, "SET SESSION AUTHORIZATION %s;\n",
8080 peter_e@gmx.net 290 : 0 : fmtId(grantor->data));
291 : :
8135 tgl@sss.pgh.pa.us 292 [ + + ]:CBC 2005 : if (privs->len > 0)
293 : : {
1370 294 : 2003 : appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
295 : : prefix, privs->data, type);
2749 296 [ + + + - ]: 2003 : if (nspname && *nspname)
1370 297 : 1714 : appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
999 jdavis@postgresql.or 298 [ + - + + ]: 2003 : if (name && *name)
299 : 1893 : appendPQExpBuffer(thissql, "%s ", name);
300 : 2003 : appendPQExpBufferStr(thissql, "TO ");
8135 tgl@sss.pgh.pa.us 301 [ + + ]: 2003 : if (grantee->len == 0)
1370 302 : 1170 : appendPQExpBufferStr(thissql, "PUBLIC;\n");
303 : : else
304 : 833 : appendPQExpBuffer(thissql, "%s;\n", fmtId(grantee->data));
305 : : }
8135 306 [ + + ]: 2005 : if (privswgo->len > 0)
307 : : {
1370 308 : 20 : appendPQExpBuffer(thissql, "%sGRANT %s ON %s ",
309 : : prefix, privswgo->data, type);
2749 310 [ + + + - ]: 20 : if (nspname && *nspname)
1370 311 : 19 : appendPQExpBuffer(thissql, "%s.", fmtId(nspname));
999 jdavis@postgresql.or 312 [ + - + - ]: 20 : if (name && *name)
313 : 20 : appendPQExpBuffer(thissql, "%s ", name);
314 : 20 : appendPQExpBufferStr(thissql, "TO ");
8135 tgl@sss.pgh.pa.us 315 [ - + ]: 20 : if (grantee->len == 0)
1370 tgl@sss.pgh.pa.us 316 :UBC 0 : appendPQExpBufferStr(thissql, "PUBLIC");
317 : : else
1370 tgl@sss.pgh.pa.us 318 :CBC 20 : appendPQExpBufferStr(thissql, fmtId(grantee->data));
319 : 20 : appendPQExpBufferStr(thissql, " WITH GRANT OPTION;\n");
320 : : }
321 : :
8080 peter_e@gmx.net 322 [ + - ]: 2005 : if (grantor->len > 0
323 [ + - - + ]: 2005 : && (!owner || strcmp(owner, grantor->data) != 0))
1370 tgl@sss.pgh.pa.us 324 :UBC 0 : appendPQExpBufferStr(thissql, "RESET SESSION AUTHORIZATION;\n");
325 : : }
326 : : }
327 : : else
328 : : {
329 : : /* parseAclItem failed, give up */
330 : 0 : ok = false;
331 : 0 : break;
332 : : }
333 : : }
334 : :
8135 tgl@sss.pgh.pa.us 335 :CBC 28390 : destroyPQExpBuffer(grantee);
336 : 28390 : destroyPQExpBuffer(grantor);
337 : 28390 : destroyPQExpBuffer(privs);
338 : 28390 : destroyPQExpBuffer(privswgo);
339 : :
8080 peter_e@gmx.net 340 : 28390 : appendPQExpBuffer(sql, "%s%s", firstsql->data, secondsql->data);
341 : 28390 : destroyPQExpBuffer(firstsql);
342 : 28390 : destroyPQExpBuffer(secondsql);
343 : :
1178 peter@eisentraut.org 344 : 28390 : free(aclitems);
345 : 28390 : free(baseitems);
346 : 28390 : free(grantitems);
347 : 28390 : free(revokeitems);
348 : :
3860 tgl@sss.pgh.pa.us 349 : 28390 : return ok;
350 : : }
351 : :
352 : : /*
353 : : * Build ALTER DEFAULT PRIVILEGES command(s) for a single pg_default_acl entry.
354 : : *
355 : : * type: the object type (TABLES, FUNCTIONS, etc)
356 : : * nspname: schema name, or NULL for global default privileges
357 : : * acls: the ACL string fetched from the database
358 : : * acldefault: the appropriate default ACL for the object type and owner
359 : : * owner: username of privileges owner (will be passed through fmtId)
360 : : * remoteVersion: version of database
361 : : *
362 : : * Returns true if okay, false if could not parse the acl string.
363 : : * The resulting commands (if any) are appended to the contents of 'sql'.
364 : : */
365 : : bool
5815 366 : 156 : buildDefaultACLCommands(const char *type, const char *nspname,
367 : : const char *acls, const char *acldefault,
368 : : const char *owner,
369 : : int remoteVersion,
370 : : PQExpBuffer sql)
371 : : {
372 : : PQExpBuffer prefix;
373 : :
374 : 156 : prefix = createPQExpBuffer();
375 : :
376 : : /*
377 : : * We incorporate the target role directly into the command, rather than
378 : : * playing around with SET ROLE or anything like that. This is so that a
379 : : * permissions error leads to nothing happening, rather than changing
380 : : * default privileges for the wrong user.
381 : : */
382 : 156 : appendPQExpBuffer(prefix, "ALTER DEFAULT PRIVILEGES FOR ROLE %s ",
383 : : fmtId(owner));
384 [ + + ]: 156 : if (nspname)
385 : 74 : appendPQExpBuffer(prefix, "IN SCHEMA %s ", fmtId(nspname));
386 : :
387 : : /*
388 : : * There's no such thing as initprivs for a default ACL, so the base ACL
389 : : * is always just the object-type-specific default.
390 : : */
2749 391 [ - + ]: 156 : if (!buildACLCommands("", NULL, NULL, type,
392 : : acls, acldefault, owner,
3140 sfrost@snowman.net 393 : 156 : prefix->data, remoteVersion, sql))
394 : : {
3045 sfrost@snowman.net 395 :UBC 0 : destroyPQExpBuffer(prefix);
3140 396 : 0 : return false;
397 : : }
398 : :
5815 tgl@sss.pgh.pa.us 399 :CBC 156 : destroyPQExpBuffer(prefix);
400 : :
3140 sfrost@snowman.net 401 : 156 : return true;
402 : : }
403 : :
404 : : /*
405 : : * This will parse an aclitem string, having the general form
406 : : * username=privilegecodes/grantor
407 : : *
408 : : * Returns true on success, false on parse error. On success, the components
409 : : * of the string are returned in the PQExpBuffer parameters.
410 : : *
411 : : * The returned grantee string will be the dequoted username, or an empty
412 : : * string in the case of a grant to PUBLIC. The returned grantor is the
413 : : * dequoted grantor name. Privilege characters are translated to GRANT/REVOKE
414 : : * comma-separated privileges lists. If "privswgo" is non-NULL, the result is
415 : : * separate lists for privileges with grant option ("privswgo") and without
416 : : * ("privs"). Otherwise, "privs" bears every relevant privilege, ignoring the
417 : : * grant option distinction.
418 : : *
419 : : * Note: for cross-version compatibility, it's important to use ALL to
420 : : * represent the privilege sets whenever appropriate.
421 : : */
422 : : static bool
6071 tgl@sss.pgh.pa.us 423 : 2401 : parseAclItem(const char *item, const char *type,
424 : : const char *name, const char *subname, int remoteVersion,
425 : : PQExpBuffer grantee, PQExpBuffer grantor,
426 : : PQExpBuffer privs, PQExpBuffer privswgo)
427 : : {
428 : : char *buf;
8135 429 : 2401 : bool all_with_go = true;
430 : 2401 : bool all_without_go = true;
431 : : char *eqpos;
432 : : char *slpos;
433 : : char *pos;
434 : :
2317 michael@paquier.xyz 435 : 2401 : buf = pg_strdup(item);
436 : :
437 : : /* user or group name is string up to = */
1370 tgl@sss.pgh.pa.us 438 : 2401 : eqpos = dequoteAclUserName(grantee, buf);
8073 439 [ - + ]: 2401 : if (*eqpos != '=')
440 : : {
2317 michael@paquier.xyz 441 :UBC 0 : pg_free(buf);
8135 tgl@sss.pgh.pa.us 442 : 0 : return false;
443 : : }
444 : :
445 : : /* grantor should appear after / */
8135 tgl@sss.pgh.pa.us 446 :CBC 2401 : slpos = strchr(eqpos + 1, '/');
447 [ + - ]: 2401 : if (slpos)
448 : : {
8073 449 : 2401 : *slpos++ = '\0';
1370 450 : 2401 : slpos = dequoteAclUserName(grantor, slpos);
8073 451 [ - + ]: 2401 : if (*slpos != '\0')
452 : : {
2317 michael@paquier.xyz 453 :UBC 0 : pg_free(buf);
8073 tgl@sss.pgh.pa.us 454 : 0 : return false;
455 : : }
456 : : }
457 : : else
458 : : {
2317 michael@paquier.xyz 459 : 0 : pg_free(buf);
3251 tgl@sss.pgh.pa.us 460 : 0 : return false;
461 : : }
462 : :
463 : : /* privilege codes */
464 : : #define CONVERT_PRIV(code, keywd) \
465 : : do { \
466 : : if ((pos = strchr(eqpos + 1, code))) \
467 : : { \
468 : : if (*(pos + 1) == '*' && privswgo != NULL) \
469 : : { \
470 : : AddAcl(privswgo, keywd, subname); \
471 : : all_without_go = false; \
472 : : } \
473 : : else \
474 : : { \
475 : : AddAcl(privs, keywd, subname); \
476 : : all_with_go = false; \
477 : : } \
478 : : } \
479 : : else \
480 : : all_with_go = all_without_go = false; \
481 : : } while (0)
482 : :
8135 tgl@sss.pgh.pa.us 483 :CBC 2401 : resetPQExpBuffer(privs);
484 : 2401 : resetPQExpBuffer(privswgo);
485 : :
5808 486 [ + + + + ]: 2401 : if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 ||
487 [ + + - + ]: 782 : strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0)
488 : : {
8135 489 [ + + - + : 1728 : CONVERT_PRIV('r', "SELECT");
- - ]
490 : :
5808 491 [ + + ]: 1728 : if (strcmp(type, "SEQUENCE") == 0 ||
492 [ - + ]: 1673 : strcmp(type, "SEQUENCES") == 0)
493 : : /* sequence only */
7168 bruce@momjian.us 494 [ + + + + : 109 : CONVERT_PRIV('U', "USAGE");
+ + ]
495 : : else
496 : : {
497 : : /* table only */
498 [ + + - + : 1673 : CONVERT_PRIV('a', "INSERT");
- - ]
3251 tgl@sss.pgh.pa.us 499 [ + + - + : 1673 : CONVERT_PRIV('x', "REFERENCES");
- - ]
500 : : /* rest are not applicable to columns */
6071 501 [ + + ]: 1673 : if (subname == NULL)
502 : : {
3251 503 [ + + - + : 433 : CONVERT_PRIV('d', "DELETE");
- - ]
504 [ + + - + : 433 : CONVERT_PRIV('t', "TRIGGER");
- - ]
1362 505 [ + + - + : 433 : CONVERT_PRIV('D', "TRUNCATE");
- - ]
542 nathan@postgresql.or 506 [ + + - + : 433 : CONVERT_PRIV('m', "MAINTAIN");
- - ]
507 : : }
508 : : }
509 : :
510 : : /* UPDATE */
3251 tgl@sss.pgh.pa.us 511 [ + + + + : 1845 : CONVERT_PRIV('w', "UPDATE");
+ + ]
512 : : }
5808 513 [ + + ]: 673 : else if (strcmp(type, "FUNCTION") == 0 ||
514 [ + + ]: 508 : strcmp(type, "FUNCTIONS") == 0)
8135 515 [ + - + + : 476 : CONVERT_PRIV('X', "EXECUTE");
+ + ]
2837 peter_e@gmx.net 516 [ + - ]: 435 : else if (strcmp(type, "PROCEDURE") == 0 ||
517 [ - + ]: 435 : strcmp(type, "PROCEDURES") == 0)
2837 peter_e@gmx.net 518 [ # # # # :UBC 0 : CONVERT_PRIV('X', "EXECUTE");
# # ]
8135 tgl@sss.pgh.pa.us 519 [ + + ]:CBC 435 : else if (strcmp(type, "LANGUAGE") == 0)
520 [ + - - + : 43 : CONVERT_PRIV('U', "USAGE");
- - ]
3084 teodor@sigaev.ru 521 [ + + ]: 392 : else if (strcmp(type, "SCHEMA") == 0 ||
3035 tgl@sss.pgh.pa.us 522 [ - + ]: 298 : strcmp(type, "SCHEMAS") == 0)
523 : : {
8135 524 [ + + - + : 94 : CONVERT_PRIV('C', "CREATE");
- - ]
525 [ + - - + : 188 : CONVERT_PRIV('U', "USAGE");
- - ]
526 : : }
527 [ + + ]: 298 : else if (strcmp(type, "DATABASE") == 0)
528 : : {
529 [ + + - + : 26 : CONVERT_PRIV('C', "CREATE");
- - ]
7069 530 [ + + - + : 26 : CONVERT_PRIV('c', "CONNECT");
- - ]
8135 531 [ + + - + : 26 : CONVERT_PRIV('T', "TEMPORARY");
- - ]
532 : : }
7750 533 [ - + ]: 272 : else if (strcmp(type, "TABLESPACE") == 0)
7750 tgl@sss.pgh.pa.us 534 [ # # # # :UBC 0 : CONVERT_PRIV('C', "CREATE");
# # ]
4654 tgl@sss.pgh.pa.us 535 [ + + ]:CBC 272 : else if (strcmp(type, "TYPE") == 0 ||
536 [ + + ]: 123 : strcmp(type, "TYPES") == 0)
537 [ + - - + : 318 : CONVERT_PRIV('U', "USAGE");
- - ]
6105 peter_e@gmx.net 538 [ + + ]: 113 : else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0)
539 [ + - - + : 36 : CONVERT_PRIV('U', "USAGE");
- - ]
5666 heikki.linnakangas@i 540 [ + + ]: 77 : else if (strcmp(type, "FOREIGN SERVER") == 0)
6105 peter_e@gmx.net 541 [ + - - + : 36 : CONVERT_PRIV('U', "USAGE");
- - ]
5362 rhaas@postgresql.org 542 [ - + ]: 41 : else if (strcmp(type, "FOREIGN TABLE") == 0)
5362 rhaas@postgresql.org 543 [ # # # # :UBC 0 : CONVERT_PRIV('r', "SELECT");
# # ]
1249 tgl@sss.pgh.pa.us 544 [ + + ]:CBC 41 : else if (strcmp(type, "PARAMETER") == 0)
545 : : {
546 [ + + + + : 3 : CONVERT_PRIV('s', "SET");
+ - ]
547 [ + - + + : 3 : CONVERT_PRIV('A', "ALTER SYSTEM");
+ - ]
548 : : }
155 fujii@postgresql.org 549 [ - + ]: 38 : else if (strcmp(type, "LARGE OBJECT") == 0 ||
155 fujii@postgresql.org 550 [ # # ]:UBC 0 : strcmp(type, "LARGE OBJECTS") == 0)
551 : : {
5748 itagaki.takahiro@gma 552 [ + - - + :CBC 38 : CONVERT_PRIV('r', "SELECT");
- - ]
553 [ + - - + : 76 : CONVERT_PRIV('w', "UPDATE");
- - ]
554 : : }
555 : : else
8135 tgl@sss.pgh.pa.us 556 :UBC 0 : abort();
557 : :
558 : : #undef CONVERT_PRIV
559 : :
8135 tgl@sss.pgh.pa.us 560 [ + + ]:CBC 2401 : if (all_with_go)
561 : : {
562 : 2 : resetPQExpBuffer(privs);
563 : 2 : printfPQExpBuffer(privswgo, "ALL");
6071 564 [ - + ]: 2 : if (subname)
6071 tgl@sss.pgh.pa.us 565 :UBC 0 : appendPQExpBuffer(privswgo, "(%s)", subname);
566 : : }
8135 tgl@sss.pgh.pa.us 567 [ + + ]:CBC 2399 : else if (all_without_go)
568 : : {
569 : 649 : resetPQExpBuffer(privswgo);
570 : 649 : printfPQExpBuffer(privs, "ALL");
6071 571 [ - + ]: 649 : if (subname)
6071 tgl@sss.pgh.pa.us 572 :UBC 0 : appendPQExpBuffer(privs, "(%s)", subname);
573 : : }
574 : :
2317 michael@paquier.xyz 575 :CBC 2401 : pg_free(buf);
576 : :
8135 tgl@sss.pgh.pa.us 577 : 2401 : return true;
578 : : }
579 : :
580 : : /*
581 : : * Transfer the role name at *input into the output buffer, adding
582 : : * quoting according to the same rules as putid() in backend's acl.c.
583 : : */
584 : : void
1370 585 : 546 : quoteAclUserName(PQExpBuffer output, const char *input)
586 : : {
587 : : const char *src;
588 : 546 : bool safe = true;
589 : :
590 [ + + ]: 9324 : for (src = input; *src; src++)
591 : : {
592 : : /* This test had better match what putid() does */
593 [ + + + + ]: 8946 : if (!isalnum((unsigned char) *src) && *src != '_')
594 : : {
595 : 168 : safe = false;
596 : 168 : break;
597 : : }
598 : : }
599 [ + + ]: 546 : if (!safe)
600 : 168 : appendPQExpBufferChar(output, '"');
601 [ + + ]: 10836 : for (src = input; *src; src++)
602 : : {
603 : : /* A double quote character in a username is encoded as "" */
604 [ + + ]: 10290 : if (*src == '"')
605 : 168 : appendPQExpBufferChar(output, '"');
606 : 10290 : appendPQExpBufferChar(output, *src);
607 : : }
608 [ + + ]: 546 : if (!safe)
609 : 168 : appendPQExpBufferChar(output, '"');
610 : 546 : }
611 : :
612 : : /*
613 : : * Transfer a user or group name starting at *input into the output buffer,
614 : : * dequoting if needed. Returns a pointer to just past the input name.
615 : : * The name is taken to end at an unquoted '=' or end of string.
616 : : * Note: unlike quoteAclUserName(), this first clears the output buffer.
617 : : */
618 : : static char *
619 : 4802 : dequoteAclUserName(PQExpBuffer output, char *input)
620 : : {
8073 621 : 4802 : resetPQExpBuffer(output);
622 : :
623 [ + + + + ]: 41796 : while (*input && *input != '=')
624 : : {
625 : : /*
626 : : * If user name isn't quoted, then just add it to the output buffer
627 : : */
628 [ + + ]: 36994 : if (*input != '"')
629 : 36885 : appendPQExpBufferChar(output, *input++);
630 : : else
631 : : {
632 : : /* Otherwise, it's a quoted username */
633 : 109 : input++;
634 : : /* Loop until we come across an unescaped quote */
8059 635 [ + + + + ]: 2616 : while (!(*input == '"' && *(input + 1) != '"'))
636 : : {
8073 637 [ - + ]: 2507 : if (*input == '\0')
2999 tgl@sss.pgh.pa.us 638 :UBC 0 : return input; /* really a syntax error... */
639 : :
640 : : /*
641 : : * Quoting convention is to escape " as "". Keep this code in
642 : : * sync with putid() in backend's acl.c.
643 : : */
8059 tgl@sss.pgh.pa.us 644 [ + + + - ]:CBC 2507 : if (*input == '"' && *(input + 1) == '"')
645 : 109 : input++;
8073 646 : 2507 : appendPQExpBufferChar(output, *input++);
647 : : }
648 : 109 : input++;
649 : : }
650 : : }
651 : 4802 : return input;
652 : : }
653 : :
654 : : /*
655 : : * Append a privilege keyword to a keyword list, inserting comma if needed.
656 : : */
657 : : static void
6071 658 : 3097 : AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname)
659 : : {
8135 660 [ + + ]: 3097 : if (aclbuf->len > 0)
661 : 678 : appendPQExpBufferChar(aclbuf, ',');
4310 heikki.linnakangas@i 662 : 3097 : appendPQExpBufferStr(aclbuf, keyword);
6071 tgl@sss.pgh.pa.us 663 [ + + ]: 3097 : if (subname)
664 : 1240 : appendPQExpBuffer(aclbuf, "(%s)", subname);
8135 665 : 3097 : }
666 : :
667 : :
668 : : /*
669 : : * buildShSecLabelQuery
670 : : *
671 : : * Build a query to retrieve security labels for a shared object.
672 : : * The object is identified by its OID plus the name of the catalog
673 : : * it can be found in (e.g., "pg_database" for database names).
674 : : * The query is appended to "sql". (We don't execute it here so as to
675 : : * keep this file free of assumptions about how to deal with SQL errors.)
676 : : */
677 : : void
1838 peter@eisentraut.org 678 : 172 : buildShSecLabelQuery(const char *catalog_name, Oid objectId,
679 : : PQExpBuffer sql)
680 : : {
5162 rhaas@postgresql.org 681 : 172 : appendPQExpBuffer(sql,
682 : : "SELECT provider, label FROM pg_catalog.pg_shseclabel "
683 : : "WHERE classoid = 'pg_catalog.%s'::pg_catalog.regclass "
684 : : "AND objoid = '%u'", catalog_name, objectId);
685 : 172 : }
686 : :
687 : : /*
688 : : * emitShSecLabels
689 : : *
690 : : * Construct SECURITY LABEL commands using the data retrieved by the query
691 : : * generated by buildShSecLabelQuery, and append them to "buffer".
692 : : * Here, the target object is identified by its type name (e.g. "DATABASE")
693 : : * and its name (not pre-quoted).
694 : : */
695 : : void
696 : 172 : emitShSecLabels(PGconn *conn, PGresult *res, PQExpBuffer buffer,
697 : : const char *objtype, const char *objname)
698 : : {
699 : : int i;
700 : :
701 [ - + ]: 172 : for (i = 0; i < PQntuples(res); i++)
702 : : {
4836 bruce@momjian.us 703 :UBC 0 : char *provider = PQgetvalue(res, i, 0);
704 : 0 : char *label = PQgetvalue(res, i, 1);
705 : :
706 : : /* must use fmtId result before calling it again */
5162 rhaas@postgresql.org 707 : 0 : appendPQExpBuffer(buffer,
708 : : "SECURITY LABEL FOR %s ON %s",
709 : : fmtId(provider), objtype);
710 : 0 : appendPQExpBuffer(buffer,
711 : : " %s IS ",
712 : : fmtId(objname));
713 : 0 : appendStringLiteralConn(buffer, label, conn);
4310 heikki.linnakangas@i 714 : 0 : appendPQExpBufferStr(buffer, ";\n");
715 : : }
5162 rhaas@postgresql.org 716 :CBC 172 : }
717 : :
718 : :
719 : : /*
720 : : * Detect whether the given GUC variable is of GUC_LIST_QUOTE type.
721 : : *
722 : : * It'd be better if we could inquire this directly from the backend; but even
723 : : * if there were a function for that, it could only tell us about variables
724 : : * currently known to guc.c, so that it'd be unsafe for extensions to declare
725 : : * GUC_LIST_QUOTE variables anyway. Lacking a solution for that, it doesn't
726 : : * seem worth the work to do more than have this list, which must be kept in
727 : : * sync with the variables actually marked GUC_LIST_QUOTE in guc_parameters.dat.
728 : : */
729 : : bool
2726 tgl@sss.pgh.pa.us 730 : 70 : variable_is_guc_list_quote(const char *name)
731 : : {
1764 michael@paquier.xyz 732 [ + + + + ]: 135 : if (pg_strcasecmp(name, "local_preload_libraries") == 0 ||
733 [ + - ]: 125 : pg_strcasecmp(name, "search_path") == 0 ||
2726 tgl@sss.pgh.pa.us 734 [ + - ]: 120 : pg_strcasecmp(name, "session_preload_libraries") == 0 ||
735 [ + - ]: 120 : pg_strcasecmp(name, "shared_preload_libraries") == 0 ||
1764 michael@paquier.xyz 736 [ - + ]: 120 : pg_strcasecmp(name, "temp_tablespaces") == 0 ||
737 : 60 : pg_strcasecmp(name, "unix_socket_directories") == 0)
2726 tgl@sss.pgh.pa.us 738 : 10 : return true;
739 : : else
740 : 60 : return false;
741 : : }
742 : :
743 : : /*
744 : : * SplitGUCList --- parse a string containing identifiers or file names
745 : : *
746 : : * This is used to split the value of a GUC_LIST_QUOTE GUC variable, without
747 : : * presuming whether the elements will be taken as identifiers or file names.
748 : : * See comparable code in src/backend/utils/adt/varlena.c.
749 : : *
750 : : * Inputs:
751 : : * rawstring: the input string; must be overwritable! On return, it's
752 : : * been modified to contain the separated identifiers.
753 : : * separator: the separator punctuation expected between identifiers
754 : : * (typically '.' or ','). Whitespace may also appear around
755 : : * identifiers.
756 : : * Outputs:
757 : : * namelist: receives a malloc'd, null-terminated array of pointers to
758 : : * identifiers within rawstring. Caller should free this
759 : : * even on error return.
760 : : *
761 : : * Returns true if okay, false if there is a syntax error in the string.
762 : : */
763 : : bool
2594 764 : 10 : SplitGUCList(char *rawstring, char separator,
765 : : char ***namelist)
766 : : {
767 : 10 : char *nextp = rawstring;
768 : 10 : bool done = false;
769 : : char **nextptr;
770 : :
771 : : /*
772 : : * Since we disallow empty identifiers, this is a conservative
773 : : * overestimate of the number of pointers we could need. Allow one for
774 : : * list terminator.
775 : : */
776 : 10 : *namelist = nextptr = (char **)
777 : 10 : pg_malloc((strlen(rawstring) / 2 + 2) * sizeof(char *));
778 : 10 : *nextptr = NULL;
779 : :
780 [ - + ]: 10 : while (isspace((unsigned char) *nextp))
2594 tgl@sss.pgh.pa.us 781 :UBC 0 : nextp++; /* skip leading whitespace */
782 : :
2594 tgl@sss.pgh.pa.us 783 [ - + ]:CBC 10 : if (*nextp == '\0')
2594 tgl@sss.pgh.pa.us 784 :UBC 0 : return true; /* allow empty string */
785 : :
786 : : /* At the top of the loop, we are at start of a new identifier. */
787 : : do
788 : : {
789 : : char *curname;
790 : : char *endp;
791 : :
2594 tgl@sss.pgh.pa.us 792 [ + + ]:CBC 25 : if (*nextp == '"')
793 : : {
794 : : /* Quoted name --- collapse quote-quote pairs */
795 : 20 : curname = nextp + 1;
796 : : for (;;)
797 : : {
798 : 30 : endp = strchr(nextp + 1, '"');
799 [ - + ]: 25 : if (endp == NULL)
2594 tgl@sss.pgh.pa.us 800 :UBC 0 : return false; /* mismatched quotes */
2594 tgl@sss.pgh.pa.us 801 [ + + ]:CBC 25 : if (endp[1] != '"')
802 : 20 : break; /* found end of quoted name */
803 : : /* Collapse adjacent quotes into one quote, and look again */
804 : 5 : memmove(endp, endp + 1, strlen(endp));
805 : 5 : nextp = endp;
806 : : }
807 : : /* endp now points at the terminating quote */
808 : 20 : nextp = endp + 1;
809 : : }
810 : : else
811 : : {
812 : : /* Unquoted name --- extends to separator or whitespace */
813 : 5 : curname = nextp;
814 [ + + + - ]: 55 : while (*nextp && *nextp != separator &&
815 [ + - ]: 50 : !isspace((unsigned char) *nextp))
816 : 50 : nextp++;
817 : 5 : endp = nextp;
818 [ - + ]: 5 : if (curname == nextp)
2594 tgl@sss.pgh.pa.us 819 :UBC 0 : return false; /* empty unquoted name not allowed */
820 : : }
821 : :
2594 tgl@sss.pgh.pa.us 822 [ - + ]:CBC 25 : while (isspace((unsigned char) *nextp))
2594 tgl@sss.pgh.pa.us 823 :UBC 0 : nextp++; /* skip trailing whitespace */
824 : :
2594 tgl@sss.pgh.pa.us 825 [ + + ]:CBC 25 : if (*nextp == separator)
826 : : {
827 : 15 : nextp++;
828 [ + + ]: 30 : while (isspace((unsigned char) *nextp))
829 : 15 : nextp++; /* skip leading whitespace for next */
830 : : /* we expect another name, so done remains false */
831 : : }
832 [ + - ]: 10 : else if (*nextp == '\0')
833 : 10 : done = true;
834 : : else
2594 tgl@sss.pgh.pa.us 835 :UBC 0 : return false; /* invalid syntax */
836 : :
837 : : /* Now safe to overwrite separator with a null */
2594 tgl@sss.pgh.pa.us 838 :CBC 25 : *endp = '\0';
839 : :
840 : : /*
841 : : * Finished isolating current name --- add it to output array
842 : : */
843 : 25 : *nextptr++ = curname;
844 : :
845 : : /* Loop back if we didn't reach end of string */
846 [ + + ]: 25 : } while (!done);
847 : :
848 : 10 : *nextptr = NULL;
849 : 10 : return true;
850 : : }
851 : :
852 : : /*
853 : : * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands.
854 : : *
855 : : * Parse the contents of configitem (a "name=value" string), wrap it in
856 : : * a complete ALTER command, and append it to buf.
857 : : *
858 : : * type is DATABASE or ROLE, and name is the name of the database or role.
859 : : * If we need an "IN" clause, type2 and name2 similarly define what to put
860 : : * there; otherwise they should be NULL.
861 : : * conn is used only to determine string-literal quoting conventions.
862 : : */
863 : : void
2784 864 : 35 : makeAlterConfigCommand(PGconn *conn, const char *configitem,
865 : : const char *type, const char *name,
866 : : const char *type2, const char *name2,
867 : : PQExpBuffer buf)
868 : : {
869 : : char *mine;
870 : : char *pos;
871 : :
872 : : /* Parse the configitem. If we can't find an "=", silently do nothing. */
873 : 35 : mine = pg_strdup(configitem);
874 : 35 : pos = strchr(mine, '=');
875 [ - + ]: 35 : if (pos == NULL)
876 : : {
2784 tgl@sss.pgh.pa.us 877 :UBC 0 : pg_free(mine);
878 : 0 : return;
879 : : }
2784 tgl@sss.pgh.pa.us 880 :CBC 35 : *pos++ = '\0';
881 : :
882 : : /* Build the command, with suitable quoting for everything. */
883 : 35 : appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
884 [ - + - - ]: 35 : if (type2 != NULL && name2 != NULL)
2784 tgl@sss.pgh.pa.us 885 :UBC 0 : appendPQExpBuffer(buf, "IN %s %s ", type2, fmtId(name2));
2784 tgl@sss.pgh.pa.us 886 :CBC 35 : appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine));
887 : :
888 : : /*
889 : : * Variables that are marked GUC_LIST_QUOTE were already fully quoted by
890 : : * flatten_set_variable_args() before they were put into the setconfig
891 : : * array. However, because the quoting rules used there aren't exactly
892 : : * like SQL's, we have to break the list value apart and then quote the
893 : : * elements as string literals. (The elements may be double-quoted as-is,
894 : : * but we can't just feed them to the SQL parser; it would do the wrong
895 : : * thing with elements that are zero-length or longer than NAMEDATALEN.)
896 : : *
897 : : * Variables that are not so marked should just be emitted as simple
898 : : * string literals. If the variable is not known to
899 : : * variable_is_guc_list_quote(), we'll do that; this makes it unsafe to
900 : : * use GUC_LIST_QUOTE for extension variables.
901 : : */
2726 902 [ - + ]: 35 : if (variable_is_guc_list_quote(mine))
903 : : {
904 : : char **namelist;
905 : : char **nameptr;
906 : :
907 : : /* Parse string into list of identifiers */
908 : : /* this shouldn't fail really */
2594 tgl@sss.pgh.pa.us 909 [ # # ]:UBC 0 : if (SplitGUCList(pos, ',', &namelist))
910 : : {
911 [ # # ]: 0 : for (nameptr = namelist; *nameptr; nameptr++)
912 : : {
913 [ # # ]: 0 : if (nameptr != namelist)
914 : 0 : appendPQExpBufferStr(buf, ", ");
915 : 0 : appendStringLiteralConn(buf, *nameptr, conn);
916 : : }
917 : : }
918 : 0 : pg_free(namelist);
919 : : }
920 : : else
2784 tgl@sss.pgh.pa.us 921 :CBC 35 : appendStringLiteralConn(buf, pos, conn);
922 : :
923 : 35 : appendPQExpBufferStr(buf, ";\n");
924 : :
925 : 35 : pg_free(mine);
926 : : }
927 : :
928 : : /*
929 : : * create_or_open_dir
930 : : *
931 : : * This will create a new directory with the given dirname. If there is
932 : : * already an empty directory with that name, then use it.
933 : : */
934 : : void
149 andrew@dunslane.net 935 : 11 : create_or_open_dir(const char *dirname)
936 : : {
937 : : int ret;
938 : :
939 [ - + - - ]: 11 : switch ((ret = pg_check_dir(dirname)))
940 : : {
149 andrew@dunslane.net 941 :UBC 0 : case -1:
942 : : /* opendir failed but not with ENOENT */
943 : 0 : pg_fatal("could not open directory \"%s\": %m", dirname);
944 : : break;
149 andrew@dunslane.net 945 :CBC 11 : case 0:
946 : : /* directory does not exist */
947 [ - + ]: 11 : if (mkdir(dirname, pg_dir_create_mode) < 0)
149 andrew@dunslane.net 948 :UBC 0 : pg_fatal("could not create directory \"%s\": %m", dirname);
149 andrew@dunslane.net 949 :CBC 11 : break;
149 andrew@dunslane.net 950 :UBC 0 : case 1:
951 : : /* exists and is empty, fix perms */
952 [ # # ]: 0 : if (chmod(dirname, pg_dir_create_mode) != 0)
953 : 0 : pg_fatal("could not change permissions of directory \"%s\": %m",
954 : : dirname);
955 : 0 : break;
956 : 0 : default:
957 : : /* exists and is not empty */
958 : 0 : pg_fatal("directory \"%s\" is not empty", dirname);
959 : : }
149 andrew@dunslane.net 960 :CBC 11 : }
961 : :
962 : : /*
963 : : * Generates a valid restrict key (i.e., an alphanumeric string) for use with
964 : : * psql's \restrict and \unrestrict meta-commands. For safety, the value is
965 : : * chosen at random.
966 : : */
967 : : char *
26 nathan@postgresql.or 968 : 188 : generate_restrict_key(void)
969 : : {
970 : : uint8 buf[64];
971 : 188 : char *ret = palloc(sizeof(buf));
972 : :
973 [ - + ]: 188 : if (!pg_strong_random(buf, sizeof(buf)))
26 nathan@postgresql.or 974 :UBC 0 : return NULL;
975 : :
26 nathan@postgresql.or 976 [ + + ]:CBC 12032 : for (int i = 0; i < sizeof(buf) - 1; i++)
977 : : {
978 : 11844 : uint8 idx = buf[i] % strlen(restrict_chars);
979 : :
980 : 11844 : ret[i] = restrict_chars[idx];
981 : : }
982 : 188 : ret[sizeof(buf) - 1] = '\0';
983 : :
984 : 188 : return ret;
985 : : }
986 : :
987 : : /*
988 : : * Checks that a given restrict key (intended for use with psql's \restrict and
989 : : * \unrestrict meta-commands) contains only alphanumeric characters.
990 : : */
991 : : bool
992 : 220 : valid_restrict_key(const char *restrict_key)
993 : : {
994 : 220 : return restrict_key != NULL &&
995 [ + - + - ]: 440 : restrict_key[0] != '\0' &&
996 [ + - ]: 220 : strspn(restrict_key, restrict_chars) == strlen(restrict_key);
997 : : }
|