Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * encode.c
4 : : * Various data encoding/decoding things.
5 : : *
6 : : * Copyright (c) 2001-2025, PostgreSQL Global Development Group
7 : : *
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/utils/adt/encode.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : : #include "postgres.h"
15 : :
16 : : #include <ctype.h>
17 : :
18 : : #include "mb/pg_wchar.h"
19 : : #include "port/simd.h"
20 : : #include "utils/builtins.h"
21 : : #include "utils/memutils.h"
22 : : #include "varatt.h"
23 : :
24 : :
25 : : /*
26 : : * Encoding conversion API.
27 : : * encode_len() and decode_len() compute the amount of space needed, while
28 : : * encode() and decode() perform the actual conversions. It is okay for
29 : : * the _len functions to return an overestimate, but not an underestimate.
30 : : * (Having said that, large overestimates could cause unnecessary errors,
31 : : * so it's better to get it right.) The conversion routines write to the
32 : : * buffer at *res and return the true length of their output.
33 : : */
34 : : struct pg_encoding
35 : : {
36 : : uint64 (*encode_len) (const char *data, size_t dlen);
37 : : uint64 (*decode_len) (const char *data, size_t dlen);
38 : : uint64 (*encode) (const char *data, size_t dlen, char *res);
39 : : uint64 (*decode) (const char *data, size_t dlen, char *res);
40 : : };
41 : :
42 : : static const struct pg_encoding *pg_find_encoding(const char *name);
43 : :
44 : : /*
45 : : * SQL functions.
46 : : */
47 : :
48 : : Datum
8874 bruce@momjian.us 49 :CBC 262706 : binary_encode(PG_FUNCTION_ARGS)
50 : : {
3152 noah@leadboat.com 51 : 262706 : bytea *data = PG_GETARG_BYTEA_PP(0);
8874 bruce@momjian.us 52 : 262706 : Datum name = PG_GETARG_DATUM(1);
53 : : text *result;
54 : : char *namebuf;
55 : : char *dataptr;
56 : : size_t datalen;
57 : : uint64 resultlen;
58 : : uint64 res;
59 : : const struct pg_encoding *enc;
60 : :
6426 tgl@sss.pgh.pa.us 61 : 262706 : namebuf = TextDatumGetCString(name);
62 : :
8874 bruce@momjian.us 63 : 262706 : enc = pg_find_encoding(namebuf);
64 [ - + ]: 262706 : if (enc == NULL)
8129 tgl@sss.pgh.pa.us 65 [ # # ]:UBC 0 : ereport(ERROR,
66 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
67 : : errmsg("unrecognized encoding: \"%s\"", namebuf)));
68 : :
2030 tgl@sss.pgh.pa.us 69 [ - + ]:CBC 262706 : dataptr = VARDATA_ANY(data);
70 [ - + - - : 262706 : datalen = VARSIZE_ANY_EXHDR(data);
- - - - -
+ ]
71 : :
72 : 262706 : resultlen = enc->encode_len(dataptr, datalen);
73 : :
74 : : /*
75 : : * resultlen possibly overflows uint32, therefore on 32-bit machines it's
76 : : * unsafe to rely on palloc's internal check.
77 : : */
78 [ - + ]: 262706 : if (resultlen > MaxAllocSize - VARHDRSZ)
2030 tgl@sss.pgh.pa.us 79 [ # # ]:UBC 0 : ereport(ERROR,
80 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
81 : : errmsg("result of encoding conversion is too large")));
82 : :
8874 bruce@momjian.us 83 :CBC 262706 : result = palloc(VARHDRSZ + resultlen);
84 : :
1531 michael@paquier.xyz 85 : 262706 : res = enc->encode(dataptr, datalen, VARDATA(result));
86 : :
87 : : /* Make this FATAL 'cause we've trodden on memory ... */
88 [ - + ]: 262706 : if (res > resultlen)
1531 michael@paquier.xyz 89 [ # # ]:UBC 0 : elog(FATAL, "overflow - encode estimate too small");
90 : :
6818 tgl@sss.pgh.pa.us 91 :CBC 262706 : SET_VARSIZE(result, VARHDRSZ + res);
92 : :
8874 bruce@momjian.us 93 : 262706 : PG_RETURN_TEXT_P(result);
94 : : }
95 : :
96 : : Datum
97 : 16486 : binary_decode(PG_FUNCTION_ARGS)
98 : : {
3152 noah@leadboat.com 99 : 16486 : text *data = PG_GETARG_TEXT_PP(0);
8874 bruce@momjian.us 100 : 16486 : Datum name = PG_GETARG_DATUM(1);
101 : : bytea *result;
102 : : char *namebuf;
103 : : char *dataptr;
104 : : size_t datalen;
105 : : uint64 resultlen;
106 : : uint64 res;
107 : : const struct pg_encoding *enc;
108 : :
6426 tgl@sss.pgh.pa.us 109 : 16486 : namebuf = TextDatumGetCString(name);
110 : :
8874 bruce@momjian.us 111 : 16486 : enc = pg_find_encoding(namebuf);
112 [ - + ]: 16486 : if (enc == NULL)
8129 tgl@sss.pgh.pa.us 113 [ # # ]:UBC 0 : ereport(ERROR,
114 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
115 : : errmsg("unrecognized encoding: \"%s\"", namebuf)));
116 : :
2030 tgl@sss.pgh.pa.us 117 [ - + ]:CBC 16486 : dataptr = VARDATA_ANY(data);
118 [ - + - - : 16486 : datalen = VARSIZE_ANY_EXHDR(data);
- - - - -
+ ]
119 : :
120 : 16486 : resultlen = enc->decode_len(dataptr, datalen);
121 : :
122 : : /*
123 : : * resultlen possibly overflows uint32, therefore on 32-bit machines it's
124 : : * unsafe to rely on palloc's internal check.
125 : : */
126 [ - + ]: 16486 : if (resultlen > MaxAllocSize - VARHDRSZ)
2030 tgl@sss.pgh.pa.us 127 [ # # ]:UBC 0 : ereport(ERROR,
128 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
129 : : errmsg("result of decoding conversion is too large")));
130 : :
8874 bruce@momjian.us 131 :CBC 16486 : result = palloc(VARHDRSZ + resultlen);
132 : :
1531 michael@paquier.xyz 133 : 16486 : res = enc->decode(dataptr, datalen, VARDATA(result));
134 : :
135 : : /* Make this FATAL 'cause we've trodden on memory ... */
136 [ - + ]: 16477 : if (res > resultlen)
1531 michael@paquier.xyz 137 [ # # ]:UBC 0 : elog(FATAL, "overflow - decode estimate too small");
138 : :
6818 tgl@sss.pgh.pa.us 139 :CBC 16477 : SET_VARSIZE(result, VARHDRSZ + res);
140 : :
8874 bruce@momjian.us 141 : 16477 : PG_RETURN_BYTEA_P(result);
142 : : }
143 : :
144 : :
145 : : /*
146 : : * HEX
147 : : */
148 : :
149 : : /*
150 : : * The hex expansion of each possible byte value (two chars per value).
151 : : */
152 : : static const char hextbl[512] =
153 : : "000102030405060708090a0b0c0d0e0f"
154 : : "101112131415161718191a1b1c1d1e1f"
155 : : "202122232425262728292a2b2c2d2e2f"
156 : : "303132333435363738393a3b3c3d3e3f"
157 : : "404142434445464748494a4b4c4d4e4f"
158 : : "505152535455565758595a5b5c5d5e5f"
159 : : "606162636465666768696a6b6c6d6e6f"
160 : : "707172737475767778797a7b7c7d7e7f"
161 : : "808182838485868788898a8b8c8d8e8f"
162 : : "909192939495969798999a9b9c9d9e9f"
163 : : "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
164 : : "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
165 : : "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
166 : : "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
167 : : "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
168 : : "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
169 : :
170 : : static const int8 hexlookup[128] = {
171 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
172 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
173 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
174 : : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
175 : : -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
176 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
177 : : -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
178 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
179 : : };
180 : :
181 : : static inline uint64
22 nathan@postgresql.or 182 :GNC 701150 : hex_encode_scalar(const char *src, size_t len, char *dst)
183 : : {
1531 michael@paquier.xyz 184 :CBC 701150 : const char *end = src + len;
185 : :
186 [ + + ]: 1362866 : while (src < end)
187 : : {
286 john.naylor@postgres 188 : 661716 : unsigned char usrc = *((const unsigned char *) src);
189 : :
190 : 661716 : memcpy(dst, &hextbl[2 * usrc], 2);
1531 michael@paquier.xyz 191 : 661716 : src++;
286 john.naylor@postgres 192 : 661716 : dst += 2;
193 : : }
1531 michael@paquier.xyz 194 : 701150 : return (uint64) len * 2;
195 : : }
196 : :
197 : : uint64
22 nathan@postgresql.or 198 :GNC 701150 : hex_encode(const char *src, size_t len, char *dst)
199 : : {
200 : : #ifdef USE_NO_SIMD
201 : : return hex_encode_scalar(src, len, dst);
202 : : #else
203 : 701150 : const uint64 tail_idx = len & ~(sizeof(Vector8) - 1);
204 : : uint64 i;
205 : :
206 : : /*
207 : : * This splits the high and low nibbles of each byte into separate
208 : : * vectors, adds the vectors to a mask that converts the nibbles to their
209 : : * equivalent ASCII bytes, and interleaves those bytes back together to
210 : : * form the final hex-encoded string.
211 : : */
212 [ + + ]: 1797470 : for (i = 0; i < tail_idx; i += sizeof(Vector8))
213 : : {
214 : : Vector8 srcv;
215 : : Vector8 lo;
216 : : Vector8 hi;
217 : : Vector8 mask;
218 : :
219 : 1096320 : vector8_load(&srcv, (const uint8 *) &src[i]);
220 : :
221 : 1096320 : lo = vector8_and(srcv, vector8_broadcast(0x0f));
222 : 1096320 : mask = vector8_gt(lo, vector8_broadcast(0x9));
223 : 1096320 : mask = vector8_and(mask, vector8_broadcast('a' - '0' - 10));
224 : 1096320 : mask = vector8_add(mask, vector8_broadcast('0'));
225 : 1096320 : lo = vector8_add(lo, mask);
226 : :
227 : 1096320 : hi = vector8_and(srcv, vector8_broadcast(0xf0));
228 : 1096320 : hi = vector8_shift_right(hi, 4);
229 : 1096320 : mask = vector8_gt(hi, vector8_broadcast(0x9));
230 : 1096320 : mask = vector8_and(mask, vector8_broadcast('a' - '0' - 10));
231 : 1096320 : mask = vector8_add(mask, vector8_broadcast('0'));
232 : 1096320 : hi = vector8_add(hi, mask);
233 : :
234 : 1096320 : vector8_store((uint8 *) &dst[i * 2],
235 : : vector8_interleave_low(hi, lo));
236 : 1096320 : vector8_store((uint8 *) &dst[i * 2 + sizeof(Vector8)],
237 : : vector8_interleave_high(hi, lo));
238 : : }
239 : :
240 : 701150 : (void) hex_encode_scalar(src + i, len - i, dst + i * 2);
241 : :
242 : 701150 : return (uint64) len * 2;
243 : : #endif
244 : : }
245 : :
246 : : static inline bool
1049 tgl@sss.pgh.pa.us 247 :CBC 38993 : get_hex(const char *cp, char *out)
248 : : {
1531 michael@paquier.xyz 249 : 38993 : unsigned char c = (unsigned char) *cp;
250 : 38993 : int res = -1;
251 : :
252 [ + - ]: 38993 : if (c < 127)
253 : 38993 : res = hexlookup[c];
254 : :
1049 tgl@sss.pgh.pa.us 255 : 38993 : *out = (char) res;
256 : :
257 : 38993 : return (res >= 0);
258 : : }
259 : :
260 : : uint64
1531 michael@paquier.xyz 261 : 16412 : hex_decode(const char *src, size_t len, char *dst)
262 : : {
1049 tgl@sss.pgh.pa.us 263 : 16412 : return hex_decode_safe(src, len, dst, NULL);
264 : : }
265 : :
266 : : static inline uint64
22 nathan@postgresql.or 267 :GNC 72346 : hex_decode_safe_scalar(const char *src, size_t len, char *dst, Node *escontext)
268 : : {
269 : : const char *s,
270 : : *srcend;
271 : : char v1,
272 : : v2,
273 : : *p;
274 : :
1531 michael@paquier.xyz 275 :CBC 72346 : srcend = src + len;
276 : 72346 : s = src;
277 : 72346 : p = dst;
278 [ + + ]: 91946 : while (s < srcend)
279 : : {
280 [ + + + - : 19639 : if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
+ - - + ]
281 : : {
282 : 126 : s++;
283 : 126 : continue;
284 : : }
1049 tgl@sss.pgh.pa.us 285 [ + + ]: 19513 : if (!get_hex(s, &v1))
1049 tgl@sss.pgh.pa.us 286 [ + - ]:GBC 24 : ereturn(escontext, 0,
287 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
288 : : errmsg("invalid hexadecimal digit: \"%.*s\"",
289 : : pg_mblen(s), s)));
1531 michael@paquier.xyz 290 :CBC 19489 : s++;
291 [ + + ]: 19489 : if (s >= srcend)
1049 tgl@sss.pgh.pa.us 292 [ + + ]: 9 : ereturn(escontext, 0,
293 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
294 : : errmsg("invalid hexadecimal data: odd number of digits")));
295 [ + + ]: 19480 : if (!get_hex(s, &v2))
296 [ + - ]: 6 : ereturn(escontext, 0,
297 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
298 : : errmsg("invalid hexadecimal digit: \"%.*s\"",
299 : : pg_mblen(s), s)));
1531 michael@paquier.xyz 300 : 19474 : s++;
1049 tgl@sss.pgh.pa.us 301 : 19474 : *p++ = (v1 << 4) | v2;
302 : : }
303 : :
1531 michael@paquier.xyz 304 : 72307 : return p - dst;
305 : : }
306 : :
307 : : /*
308 : : * This helper converts each byte to its binary-equivalent nibble by
309 : : * subtraction and combines them to form the return bytes (separated by zero
310 : : * bytes). Returns false if any input bytes are outside the expected ranges of
311 : : * ASCII values. Otherwise, returns true.
312 : : */
313 : : #ifndef USE_NO_SIMD
314 : : static inline bool
22 nathan@postgresql.or 315 :GNC 246108 : hex_decode_simd_helper(const Vector8 src, Vector8 *dst)
316 : : {
317 : : Vector8 sub;
318 : 246108 : Vector8 mask_hi = vector8_interleave_low(vector8_broadcast(0), vector8_broadcast(0x0f));
319 : 246108 : Vector8 mask_lo = vector8_interleave_low(vector8_broadcast(0x0f), vector8_broadcast(0));
320 : : Vector8 tmp;
321 : : bool ret;
322 : :
323 : 246108 : tmp = vector8_gt(vector8_broadcast('9' + 1), src);
324 : 246108 : sub = vector8_and(tmp, vector8_broadcast('0'));
325 : :
326 : 246108 : tmp = vector8_gt(src, vector8_broadcast('A' - 1));
327 : 246108 : tmp = vector8_and(tmp, vector8_broadcast('A' - 10));
328 : 246108 : sub = vector8_add(sub, tmp);
329 : :
330 : 246108 : tmp = vector8_gt(src, vector8_broadcast('a' - 1));
331 : 246108 : tmp = vector8_and(tmp, vector8_broadcast('a' - 'A'));
332 : 246108 : sub = vector8_add(sub, tmp);
333 : :
334 : 246108 : *dst = vector8_issub(src, sub);
335 : 246108 : ret = !vector8_has_ge(*dst, 0x10);
336 : :
337 : 246108 : tmp = vector8_and(*dst, mask_hi);
338 : 246108 : tmp = vector8_shift_right(tmp, 8);
339 : 246108 : *dst = vector8_and(*dst, mask_lo);
340 : 246108 : *dst = vector8_shift_left(*dst, 4);
341 : 246108 : *dst = vector8_or(*dst, tmp);
342 : 246108 : return ret;
343 : : }
344 : : #endif /* ! USE_NO_SIMD */
345 : :
346 : : uint64
347 : 72346 : hex_decode_safe(const char *src, size_t len, char *dst, Node *escontext)
348 : : {
349 : : #ifdef USE_NO_SIMD
350 : : return hex_decode_safe_scalar(src, len, dst, escontext);
351 : : #else
352 : 72346 : const uint64 tail_idx = len & ~(sizeof(Vector8) * 2 - 1);
353 : : uint64 i;
354 : 72346 : bool success = true;
355 : :
356 : : /*
357 : : * We must process 2 vectors at a time since the output will be half the
358 : : * length of the input.
359 : : */
360 [ + + ]: 195400 : for (i = 0; i < tail_idx; i += sizeof(Vector8) * 2)
361 : : {
362 : : Vector8 srcv;
363 : : Vector8 dstv1;
364 : : Vector8 dstv2;
365 : :
366 : 123054 : vector8_load(&srcv, (const uint8 *) &src[i]);
367 : 123054 : success &= hex_decode_simd_helper(srcv, &dstv1);
368 : :
369 : 123054 : vector8_load(&srcv, (const uint8 *) &src[i + sizeof(Vector8)]);
370 : 123054 : success &= hex_decode_simd_helper(srcv, &dstv2);
371 : :
372 : 123054 : vector8_store((uint8 *) &dst[i / 2], vector8_pack_16(dstv1, dstv2));
373 : : }
374 : :
375 : : /*
376 : : * If something didn't look right in the vector path, try again in the
377 : : * scalar path so that we can handle it correctly.
378 : : */
379 [ + + ]: 72346 : if (!success)
380 : 27 : i = 0;
381 : :
382 : 72346 : return i / 2 + hex_decode_safe_scalar(src + i, len - i, dst + i / 2, escontext);
383 : : #endif
384 : : }
385 : :
386 : : static uint64
2030 tgl@sss.pgh.pa.us 387 :CBC 262615 : hex_enc_len(const char *src, size_t srclen)
388 : : {
1531 michael@paquier.xyz 389 : 262615 : return (uint64) srclen << 1;
390 : : }
391 : :
392 : : static uint64
2030 tgl@sss.pgh.pa.us 393 : 16412 : hex_dec_len(const char *src, size_t srclen)
394 : : {
1531 michael@paquier.xyz 395 : 16412 : return (uint64) srclen >> 1;
396 : : }
397 : :
398 : : /*
399 : : * BASE64 and BASE64URL
400 : : */
401 : :
402 : : static const char _base64[] =
403 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
404 : :
405 : : static const char _base64url[] =
406 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
407 : :
408 : : static const int8 b64lookup[128] = {
409 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
410 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
411 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
412 : : 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
413 : : -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
414 : : 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
415 : : -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
416 : : 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
417 : : };
418 : :
419 : : /*
420 : : * pg_base64_encode_internal
421 : : *
422 : : * Helper for decoding base64 or base64url. When url is passed as true the
423 : : * input will be encoded using base64url. len bytes in src is encoded into
424 : : * dst.
425 : : */
426 : : static uint64
38 dgustafsson@postgres 427 :GNC 57 : pg_base64_encode_internal(const char *src, size_t len, char *dst, bool url)
428 : : {
429 : : char *p,
8874 bruce@momjian.us 430 :CBC 57 : *lend = dst + 76;
431 : : const char *s,
1531 michael@paquier.xyz 432 : 57 : *end = src + len;
8874 bruce@momjian.us 433 : 57 : int pos = 2;
434 : 57 : uint32 buf = 0;
38 dgustafsson@postgres 435 [ + + ]:GNC 57 : const char *alphabet = url ? _base64url : _base64;
436 : :
8874 bruce@momjian.us 437 :CBC 57 : s = src;
438 : 57 : p = dst;
439 : :
440 [ + + ]: 606 : while (s < end)
441 : : {
7339 tgl@sss.pgh.pa.us 442 : 549 : buf |= (unsigned char) *s << (pos << 3);
8874 bruce@momjian.us 443 : 549 : pos--;
444 : 549 : s++;
445 : :
446 : : /* write it out */
447 [ + + ]: 549 : if (pos < 0)
448 : : {
38 dgustafsson@postgres 449 :GNC 168 : *p++ = alphabet[(buf >> 18) & 0x3f];
450 : 168 : *p++ = alphabet[(buf >> 12) & 0x3f];
451 : 168 : *p++ = alphabet[(buf >> 6) & 0x3f];
452 : 168 : *p++ = alphabet[buf & 0x3f];
453 : :
8874 bruce@momjian.us 454 :CBC 168 : pos = 2;
455 : 168 : buf = 0;
456 : :
38 dgustafsson@postgres 457 [ + + + + ]:GNC 168 : if (!url && p >= lend)
458 : : {
459 : 6 : *p++ = '\n';
460 : 6 : lend = p + 76;
461 : : }
462 : : }
463 : : }
464 : :
465 : : /* Handle remaining bytes in buf */
8874 bruce@momjian.us 466 [ + + ]:CBC 57 : if (pos != 2)
467 : : {
38 dgustafsson@postgres 468 :GNC 36 : *p++ = alphabet[(buf >> 18) & 0x3f];
469 : 36 : *p++ = alphabet[(buf >> 12) & 0x3f];
470 : :
471 [ + + ]: 36 : if (pos == 0)
472 : : {
473 : 9 : *p++ = alphabet[(buf >> 6) & 0x3f];
474 [ - + ]: 9 : if (!url)
38 dgustafsson@postgres 475 :UNC 0 : *p++ = '=';
476 : : }
38 dgustafsson@postgres 477 [ + + ]:GNC 27 : else if (!url)
478 : : {
479 : 6 : *p++ = '=';
480 : 6 : *p++ = '=';
481 : : }
482 : : }
483 : :
8874 bruce@momjian.us 484 :CBC 57 : return p - dst;
485 : : }
486 : :
487 : : static uint64
38 dgustafsson@postgres 488 :GNC 6 : pg_base64_encode(const char *src, size_t len, char *dst)
489 : : {
490 : 6 : return pg_base64_encode_internal(src, len, dst, false);
491 : : }
492 : :
493 : : static uint64
494 : 51 : pg_base64url_encode(const char *src, size_t len, char *dst)
495 : : {
496 : 51 : return pg_base64_encode_internal(src, len, dst, true);
497 : : }
498 : :
499 : : /*
500 : : * pg_base64_decode_internal
501 : : *
502 : : * Helper for decoding base64 or base64url. When url is passed as true the
503 : : * input will be assumed to be encoded using base64url.
504 : : */
505 : : static uint64
506 : 59 : pg_base64_decode_internal(const char *src, size_t len, char *dst, bool url)
507 : : {
1531 michael@paquier.xyz 508 :CBC 59 : const char *srcend = src + len,
8874 bruce@momjian.us 509 : 59 : *s = src;
7339 tgl@sss.pgh.pa.us 510 : 59 : char *p = dst;
511 : : char c;
8874 bruce@momjian.us 512 : 59 : int b = 0;
513 : 59 : uint32 buf = 0;
514 : 59 : int pos = 0,
515 : 59 : end = 0;
516 : :
517 [ + + ]: 570 : while (s < srcend)
518 : : {
519 : 517 : c = *s++;
520 : :
521 [ + - + - : 517 : if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ + - + ]
522 : 3 : continue;
523 : :
524 : : /* convert base64url to base64 */
38 dgustafsson@postgres 525 [ + + ]:GNC 514 : if (url)
526 : : {
527 [ + + ]: 210 : if (c == '-')
528 : 9 : c = '+';
529 [ + + ]: 201 : else if (c == '_')
530 : 6 : c = '/';
531 : : }
532 : :
8874 bruce@momjian.us 533 [ + + ]:CBC 514 : if (c == '=')
534 : : {
535 : : /* end sequence */
536 [ + + ]: 17 : if (!end)
537 : : {
538 [ + + ]: 11 : if (pos == 2)
539 : 6 : end = 1;
540 [ + + ]: 5 : else if (pos == 3)
541 : 2 : end = 2;
542 : : else
543 : : {
544 : : /* translator: %s is the name of an encoding scheme */
8129 tgl@sss.pgh.pa.us 545 [ + - + - ]:GBC 3 : ereport(ERROR,
546 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
547 : : errmsg("unexpected \"=\" while decoding %s sequence", url ? "base64url" : "base64")));
548 : : }
549 : : }
8874 bruce@momjian.us 550 :CBC 14 : b = 0;
551 : : }
552 : : else
553 : : {
554 : 497 : b = -1;
555 [ + - + - ]: 497 : if (c > 0 && c < 127)
7339 tgl@sss.pgh.pa.us 556 : 497 : b = b64lookup[(unsigned char) c];
8874 bruce@momjian.us 557 [ + + ]: 497 : if (b < 0)
558 : : {
559 : : /* translator: %s is the name of an encoding scheme */
8129 tgl@sss.pgh.pa.us 560 [ + - + - ]:GBC 3 : ereport(ERROR,
561 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
562 : : errmsg("invalid symbol \"%.*s\" found while decoding %s sequence",
563 : : pg_mblen(s - 1), s - 1,
564 : : url ? "base64url" : "base64")));
565 : : }
566 : : }
567 : : /* add it to buffer */
8874 bruce@momjian.us 568 :CBC 508 : buf = (buf << 6) + b;
569 : 508 : pos++;
570 [ + + ]: 508 : if (pos == 4)
571 : : {
572 : 109 : *p++ = (buf >> 16) & 255;
573 [ + + + + ]: 109 : if (end == 0 || end > 1)
574 : 103 : *p++ = (buf >> 8) & 255;
575 [ + + - + ]: 109 : if (end == 0 || end > 2)
576 : 101 : *p++ = buf & 255;
577 : 109 : buf = 0;
578 : 109 : pos = 0;
579 : : }
580 : : }
581 : :
38 dgustafsson@postgres 582 [ + + ]:GNC 53 : if (pos == 2)
583 : : {
584 : 18 : buf <<= 12;
585 : 18 : *p++ = (buf >> 16) & 0xFF;
586 : : }
587 [ + + ]: 35 : else if (pos == 3)
588 : : {
589 : 9 : buf <<= 6;
590 : 9 : *p++ = (buf >> 16) & 0xFF;
591 : 9 : *p++ = (buf >> 8) & 0xFF;
592 : : }
593 [ + + ]: 26 : else if (pos != 0)
594 : : {
595 : : /* translator: %s is the name of an encoding scheme */
8129 tgl@sss.pgh.pa.us 596 [ + - + - ]:GBC 3 : ereport(ERROR,
597 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
598 : : errmsg("invalid %s end sequence", url ? "base64url" : "base64"),
599 : : errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
600 : : }
601 : :
8874 bruce@momjian.us 602 :CBC 50 : return p - dst;
603 : : }
604 : :
605 : : static uint64
38 dgustafsson@postgres 606 :GNC 5 : pg_base64_decode(const char *src, size_t len, char *dst)
607 : : {
608 : 5 : return pg_base64_decode_internal(src, len, dst, false);
609 : : }
610 : :
611 : : static uint64
612 : 54 : pg_base64url_decode(const char *src, size_t len, char *dst)
613 : : {
614 : 54 : return pg_base64_decode_internal(src, len, dst, true);
615 : : }
616 : :
617 : : static uint64
2030 tgl@sss.pgh.pa.us 618 :CBC 6 : pg_base64_enc_len(const char *src, size_t srclen)
619 : : {
620 : : /* 3 bytes will be converted to 4, linefeed after 76 chars */
873 621 : 6 : return ((uint64) srclen + 2) / 3 * 4 + (uint64) srclen / (76 * 3 / 4);
622 : : }
623 : :
624 : : static uint64
2030 625 : 5 : pg_base64_dec_len(const char *src, size_t srclen)
626 : : {
627 : 5 : return ((uint64) srclen * 3) >> 2;
628 : : }
629 : :
630 : : static uint64
38 dgustafsson@postgres 631 :GNC 51 : pg_base64url_enc_len(const char *src, size_t srclen)
632 : : {
633 : : /*
634 : : * Unlike standard base64, base64url doesn't use padding characters when
635 : : * the input length is not divisible by 3
636 : : */
637 : 51 : return (srclen + 2) / 3 * 4;
638 : : }
639 : :
640 : : static uint64
641 : 54 : pg_base64url_dec_len(const char *src, size_t srclen)
642 : : {
643 : : /*
644 : : * For base64, each 4 characters of input produce at most 3 bytes of
645 : : * output. For base64url without padding, we need to round up to the
646 : : * nearest 4
647 : : */
648 : 54 : size_t adjusted_len = srclen;
649 : :
650 [ + + ]: 54 : if (srclen % 4 != 0)
651 : 30 : adjusted_len += 4 - (srclen % 4);
652 : :
653 : 54 : return (adjusted_len * 3) / 4;
654 : : }
655 : :
656 : : /*
657 : : * Escape
658 : : * Minimally escape bytea to text.
659 : : * De-escape text to bytea.
660 : : *
661 : : * We must escape zero bytes and high-bit-set bytes to avoid generating
662 : : * text that might be invalid in the current encoding, or that might
663 : : * change to something else if passed through an encoding conversion
664 : : * (leading to failing to de-escape to the original bytea value).
665 : : * Also of course backslash itself has to be escaped.
666 : : *
667 : : * De-escaping processes \\ and any \### octal
668 : : */
669 : :
670 : : #define VAL(CH) ((CH) - '0')
671 : : #define DIG(VAL) ((VAL) + '0')
672 : :
673 : : static uint64
1531 michael@paquier.xyz 674 :CBC 34 : esc_encode(const char *src, size_t srclen, char *dst)
675 : : {
7339 tgl@sss.pgh.pa.us 676 : 34 : const char *end = src + srclen;
677 : 34 : char *rp = dst;
2030 678 : 34 : uint64 len = 0;
679 : :
8810 bruce@momjian.us 680 [ + + ]: 328 : while (src < end)
681 : : {
6454 tgl@sss.pgh.pa.us 682 : 294 : unsigned char c = (unsigned char) *src;
683 : :
684 [ + + + + ]: 294 : if (c == '\0' || IS_HIGHBIT_SET(c))
685 : : {
8810 bruce@momjian.us 686 : 45 : rp[0] = '\\';
6454 tgl@sss.pgh.pa.us 687 : 45 : rp[1] = DIG(c >> 6);
688 : 45 : rp[2] = DIG((c >> 3) & 7);
689 : 45 : rp[3] = DIG(c & 7);
8810 bruce@momjian.us 690 : 45 : rp += 4;
691 : 45 : len += 4;
692 : : }
6454 tgl@sss.pgh.pa.us 693 [ - + ]: 249 : else if (c == '\\')
694 : : {
8810 bruce@momjian.us 695 :UBC 0 : rp[0] = '\\';
696 : 0 : rp[1] = '\\';
697 : 0 : rp += 2;
698 : 0 : len += 2;
699 : : }
700 : : else
701 : : {
6454 tgl@sss.pgh.pa.us 702 :CBC 249 : *rp++ = c;
8810 bruce@momjian.us 703 : 249 : len++;
704 : : }
705 : :
706 : 294 : src++;
707 : : }
708 : :
709 : 34 : return len;
710 : : }
711 : :
712 : : static uint64
1531 michael@paquier.xyz 713 : 15 : esc_decode(const char *src, size_t srclen, char *dst)
714 : : {
7339 tgl@sss.pgh.pa.us 715 : 15 : const char *end = src + srclen;
716 : 15 : char *rp = dst;
2030 717 : 15 : uint64 len = 0;
718 : :
8810 bruce@momjian.us 719 [ + + ]: 1200042 : while (src < end)
720 : : {
721 [ + + ]: 1200027 : if (src[0] != '\\')
722 : 1200012 : *rp++ = *src++;
8769 723 [ + - ]: 15 : else if (src + 3 < end &&
724 [ + - + - ]: 15 : (src[1] >= '0' && src[1] <= '3') &&
725 [ + - + - ]: 15 : (src[2] >= '0' && src[2] <= '7') &&
726 [ + - + - ]: 15 : (src[3] >= '0' && src[3] <= '7'))
8810 727 : 15 : {
728 : : int val;
729 : :
730 : 15 : val = VAL(src[1]);
731 : 15 : val <<= 3;
732 : 15 : val += VAL(src[2]);
733 : 15 : val <<= 3;
734 : 15 : *rp++ = val + VAL(src[3]);
735 : 15 : src += 4;
736 : : }
8769 bruce@momjian.us 737 [ # # ]:UBC 0 : else if (src + 1 < end &&
738 [ # # ]: 0 : (src[1] == '\\'))
739 : : {
8810 740 : 0 : *rp++ = '\\';
741 : 0 : src += 2;
742 : : }
743 : : else
744 : : {
745 : : /*
746 : : * One backslash, not followed by ### valid octal. Should never
747 : : * get here, since esc_dec_len does same check.
748 : : */
8129 tgl@sss.pgh.pa.us 749 [ # # ]: 0 : ereport(ERROR,
750 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
751 : : errmsg("invalid input syntax for type %s", "bytea")));
752 : : }
753 : :
8810 bruce@momjian.us 754 :CBC 1200027 : len++;
755 : : }
756 : :
757 : 15 : return len;
758 : : }
759 : :
760 : : static uint64
2030 tgl@sss.pgh.pa.us 761 : 34 : esc_enc_len(const char *src, size_t srclen)
762 : : {
7339 763 : 34 : const char *end = src + srclen;
2030 764 : 34 : uint64 len = 0;
765 : :
8810 bruce@momjian.us 766 [ + + ]: 328 : while (src < end)
767 : : {
6454 tgl@sss.pgh.pa.us 768 [ + + + + ]: 294 : if (*src == '\0' || IS_HIGHBIT_SET(*src))
8810 bruce@momjian.us 769 : 45 : len += 4;
770 [ - + ]: 249 : else if (*src == '\\')
8810 bruce@momjian.us 771 :UBC 0 : len += 2;
772 : : else
8810 bruce@momjian.us 773 :CBC 249 : len++;
774 : :
775 : 294 : src++;
776 : : }
777 : :
778 : 34 : return len;
779 : : }
780 : :
781 : : static uint64
2030 tgl@sss.pgh.pa.us 782 : 15 : esc_dec_len(const char *src, size_t srclen)
783 : : {
7339 784 : 15 : const char *end = src + srclen;
2030 785 : 15 : uint64 len = 0;
786 : :
8810 bruce@momjian.us 787 [ + + ]: 1200042 : while (src < end)
788 : : {
789 [ + + ]: 1200027 : if (src[0] != '\\')
790 : 1200012 : src++;
8769 791 [ + - ]: 15 : else if (src + 3 < end &&
792 [ + - + - ]: 15 : (src[1] >= '0' && src[1] <= '3') &&
793 [ + - + - ]: 15 : (src[2] >= '0' && src[2] <= '7') &&
794 [ + - + - ]: 15 : (src[3] >= '0' && src[3] <= '7'))
795 : : {
796 : : /*
797 : : * backslash + valid octal
798 : : */
8810 799 : 15 : src += 4;
800 : : }
8769 bruce@momjian.us 801 [ # # ]:UBC 0 : else if (src + 1 < end &&
802 [ # # ]: 0 : (src[1] == '\\'))
803 : : {
804 : : /*
805 : : * two backslashes = backslash
806 : : */
8810 807 : 0 : src += 2;
808 : : }
809 : : else
810 : : {
811 : : /*
812 : : * one backslash, not followed by ### valid octal
813 : : */
8129 tgl@sss.pgh.pa.us 814 [ # # ]: 0 : ereport(ERROR,
815 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
816 : : errmsg("invalid input syntax for type %s", "bytea")));
817 : : }
818 : :
8810 bruce@momjian.us 819 :CBC 1200027 : len++;
820 : : }
821 : 15 : return len;
822 : : }
823 : :
824 : : /*
825 : : * Common
826 : : */
827 : :
828 : : static const struct
829 : : {
830 : : const char *name;
831 : : struct pg_encoding enc;
832 : : } enclist[] =
833 : :
834 : : {
835 : : {
836 : : "hex",
837 : : {
838 : : hex_enc_len, hex_dec_len, hex_encode, hex_decode
839 : : }
840 : : },
841 : : {
842 : : "base64",
843 : : {
844 : : pg_base64_enc_len, pg_base64_dec_len, pg_base64_encode, pg_base64_decode
845 : : }
846 : : },
847 : : {
848 : : "base64url",
849 : : {
850 : : pg_base64url_enc_len, pg_base64url_dec_len, pg_base64url_encode, pg_base64url_decode
851 : : }
852 : : },
853 : : {
854 : : "escape",
855 : : {
856 : : esc_enc_len, esc_dec_len, esc_encode, esc_decode
857 : : }
858 : : },
859 : : {
860 : : NULL,
861 : : {
862 : : NULL, NULL, NULL, NULL
863 : : }
864 : : }
865 : : };
866 : :
867 : : static const struct pg_encoding *
8874 868 : 279192 : pg_find_encoding(const char *name)
869 : : {
870 : : int i;
871 : :
872 [ + - ]: 279560 : for (i = 0; enclist[i].name; i++)
7844 tgl@sss.pgh.pa.us 873 [ + + ]: 279560 : if (pg_strcasecmp(enclist[i].name, name) == 0)
8874 bruce@momjian.us 874 : 279192 : return &enclist[i].enc;
875 : :
8874 bruce@momjian.us 876 :UBC 0 : return NULL;
877 : : }
|