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-2026, 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
9063 bruce@momjian.us 49 :CBC 298194 : binary_encode(PG_FUNCTION_ARGS)
50 : : {
3341 noah@leadboat.com 51 : 298194 : bytea *data = PG_GETARG_BYTEA_PP(0);
9063 bruce@momjian.us 52 : 298194 : 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 : :
6615 tgl@sss.pgh.pa.us 61 : 298194 : namebuf = TextDatumGetCString(name);
62 : :
9063 bruce@momjian.us 63 : 298194 : enc = pg_find_encoding(namebuf);
64 [ + + ]: 298194 : if (enc == NULL)
8318 tgl@sss.pgh.pa.us 65 [ + - ]:GBC 4 : ereport(ERROR,
66 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
67 : : errmsg("unrecognized encoding: \"%s\"", namebuf),
68 : : errhint("Valid encodings are \"%s\", \"%s\", \"%s\", \"%s\", and \"%s\".",
69 : : "base32hex", "base64", "base64url", "escape", "hex")));
70 : :
2219 tgl@sss.pgh.pa.us 71 [ - + ]:CBC 298190 : dataptr = VARDATA_ANY(data);
72 [ - + - - : 298190 : datalen = VARSIZE_ANY_EXHDR(data);
- - - - -
+ ]
73 : :
74 : 298190 : resultlen = enc->encode_len(dataptr, datalen);
75 : :
76 : : /*
77 : : * resultlen possibly overflows uint32, therefore on 32-bit machines it's
78 : : * unsafe to rely on palloc's internal check.
79 : : */
80 [ - + ]: 298190 : if (resultlen > MaxAllocSize - VARHDRSZ)
2219 tgl@sss.pgh.pa.us 81 [ # # ]:UBC 0 : ereport(ERROR,
82 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
83 : : errmsg("result of encoding conversion is too large")));
84 : :
9063 bruce@momjian.us 85 :CBC 298190 : result = palloc(VARHDRSZ + resultlen);
86 : :
1720 michael@paquier.xyz 87 : 298190 : res = enc->encode(dataptr, datalen, VARDATA(result));
88 : :
89 : : /* Make this FATAL 'cause we've trodden on memory ... */
90 [ - + ]: 298190 : if (res > resultlen)
1720 michael@paquier.xyz 91 [ # # ]:UBC 0 : elog(FATAL, "overflow - encode estimate too small");
92 : :
7007 tgl@sss.pgh.pa.us 93 :CBC 298190 : SET_VARSIZE(result, VARHDRSZ + res);
94 : :
9063 bruce@momjian.us 95 : 298190 : PG_RETURN_TEXT_P(result);
96 : : }
97 : :
98 : : Datum
99 : 16705 : binary_decode(PG_FUNCTION_ARGS)
100 : : {
3341 noah@leadboat.com 101 : 16705 : text *data = PG_GETARG_TEXT_PP(0);
9063 bruce@momjian.us 102 : 16705 : Datum name = PG_GETARG_DATUM(1);
103 : : bytea *result;
104 : : char *namebuf;
105 : : char *dataptr;
106 : : size_t datalen;
107 : : uint64 resultlen;
108 : : uint64 res;
109 : : const struct pg_encoding *enc;
110 : :
6615 tgl@sss.pgh.pa.us 111 : 16705 : namebuf = TextDatumGetCString(name);
112 : :
9063 bruce@momjian.us 113 : 16705 : enc = pg_find_encoding(namebuf);
114 [ + + ]: 16705 : if (enc == NULL)
8318 tgl@sss.pgh.pa.us 115 [ + - ]:GBC 4 : ereport(ERROR,
116 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
117 : : errmsg("unrecognized encoding: \"%s\"", namebuf),
118 : : errhint("Valid encodings are \"%s\", \"%s\", \"%s\", \"%s\", and \"%s\".",
119 : : "base32hex", "base64", "base64url", "escape", "hex")));
120 : :
2219 tgl@sss.pgh.pa.us 121 [ - + ]:CBC 16701 : dataptr = VARDATA_ANY(data);
122 [ - + - - : 16701 : datalen = VARSIZE_ANY_EXHDR(data);
- - - - -
+ ]
123 : :
124 : 16701 : resultlen = enc->decode_len(dataptr, datalen);
125 : :
126 : : /*
127 : : * resultlen possibly overflows uint32, therefore on 32-bit machines it's
128 : : * unsafe to rely on palloc's internal check.
129 : : */
130 [ - + ]: 16701 : if (resultlen > MaxAllocSize - VARHDRSZ)
2219 tgl@sss.pgh.pa.us 131 [ # # ]:UBC 0 : ereport(ERROR,
132 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
133 : : errmsg("result of decoding conversion is too large")));
134 : :
9063 bruce@momjian.us 135 :CBC 16701 : result = palloc(VARHDRSZ + resultlen);
136 : :
1720 michael@paquier.xyz 137 : 16701 : res = enc->decode(dataptr, datalen, VARDATA(result));
138 : :
139 : : /* Make this FATAL 'cause we've trodden on memory ... */
140 [ - + ]: 16673 : if (res > resultlen)
1720 michael@paquier.xyz 141 [ # # ]:UBC 0 : elog(FATAL, "overflow - decode estimate too small");
142 : :
7007 tgl@sss.pgh.pa.us 143 :CBC 16673 : SET_VARSIZE(result, VARHDRSZ + res);
144 : :
9063 bruce@momjian.us 145 : 16673 : PG_RETURN_BYTEA_P(result);
146 : : }
147 : :
148 : :
149 : : /*
150 : : * HEX
151 : : */
152 : :
153 : : /*
154 : : * The hex expansion of each possible byte value (two chars per value).
155 : : */
156 : : static const char hextbl[512] =
157 : : "000102030405060708090a0b0c0d0e0f"
158 : : "101112131415161718191a1b1c1d1e1f"
159 : : "202122232425262728292a2b2c2d2e2f"
160 : : "303132333435363738393a3b3c3d3e3f"
161 : : "404142434445464748494a4b4c4d4e4f"
162 : : "505152535455565758595a5b5c5d5e5f"
163 : : "606162636465666768696a6b6c6d6e6f"
164 : : "707172737475767778797a7b7c7d7e7f"
165 : : "808182838485868788898a8b8c8d8e8f"
166 : : "909192939495969798999a9b9c9d9e9f"
167 : : "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
168 : : "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
169 : : "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
170 : : "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
171 : : "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
172 : : "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
173 : :
174 : : static const int8 hexlookup[128] = {
175 : : -1, -1, -1, -1, -1, -1, -1, -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, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
178 : : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
179 : : -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
180 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
181 : : -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
182 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
183 : : };
184 : :
185 : : static inline uint64
211 nathan@postgresql.or 186 :GNC 767448 : hex_encode_scalar(const char *src, size_t len, char *dst)
187 : : {
1720 michael@paquier.xyz 188 :CBC 767448 : const char *end = src + len;
189 : :
190 [ + + ]: 1552947 : while (src < end)
191 : : {
475 john.naylor@postgres 192 : 785499 : unsigned char usrc = *((const unsigned char *) src);
193 : :
194 : 785499 : memcpy(dst, &hextbl[2 * usrc], 2);
1720 michael@paquier.xyz 195 : 785499 : src++;
475 john.naylor@postgres 196 : 785499 : dst += 2;
197 : : }
1720 michael@paquier.xyz 198 : 767448 : return (uint64) len * 2;
199 : : }
200 : :
201 : : uint64
211 nathan@postgresql.or 202 :GNC 767448 : hex_encode(const char *src, size_t len, char *dst)
203 : : {
204 : : #ifdef USE_NO_SIMD
205 : : return hex_encode_scalar(src, len, dst);
206 : : #else
207 : 767448 : const uint64 tail_idx = len & ~(sizeof(Vector8) - 1);
208 : : uint64 i;
209 : :
210 : : /*
211 : : * This splits the high and low nibbles of each byte into separate
212 : : * vectors, adds the vectors to a mask that converts the nibbles to their
213 : : * equivalent ASCII bytes, and interleaves those bytes back together to
214 : : * form the final hex-encoded string.
215 : : */
216 [ + + ]: 1935757 : for (i = 0; i < tail_idx; i += sizeof(Vector8))
217 : : {
218 : : Vector8 srcv;
219 : : Vector8 lo;
220 : : Vector8 hi;
221 : : Vector8 mask;
222 : :
223 : 1168309 : vector8_load(&srcv, (const uint8 *) &src[i]);
224 : :
225 : 1168309 : lo = vector8_and(srcv, vector8_broadcast(0x0f));
226 : 1168309 : mask = vector8_gt(lo, vector8_broadcast(0x9));
227 : 1168309 : mask = vector8_and(mask, vector8_broadcast('a' - '0' - 10));
228 : 1168309 : mask = vector8_add(mask, vector8_broadcast('0'));
229 : 1168309 : lo = vector8_add(lo, mask);
230 : :
231 : 1168309 : hi = vector8_and(srcv, vector8_broadcast(0xf0));
232 : 1168309 : hi = vector8_shift_right(hi, 4);
233 : 1168309 : mask = vector8_gt(hi, vector8_broadcast(0x9));
234 : 1168309 : mask = vector8_and(mask, vector8_broadcast('a' - '0' - 10));
235 : 1168309 : mask = vector8_add(mask, vector8_broadcast('0'));
236 : 1168309 : hi = vector8_add(hi, mask);
237 : :
238 : 1168309 : vector8_store((uint8 *) &dst[i * 2],
239 : : vector8_interleave_low(hi, lo));
240 : 1168309 : vector8_store((uint8 *) &dst[i * 2 + sizeof(Vector8)],
241 : : vector8_interleave_high(hi, lo));
242 : : }
243 : :
244 : 767448 : (void) hex_encode_scalar(src + i, len - i, dst + i * 2);
245 : :
246 : 767448 : return (uint64) len * 2;
247 : : #endif
248 : : }
249 : :
250 : : static inline bool
1238 tgl@sss.pgh.pa.us 251 :CBC 44664 : get_hex(const char *cp, char *out)
252 : : {
1720 michael@paquier.xyz 253 : 44664 : unsigned char c = (unsigned char) *cp;
254 : 44664 : int res = -1;
255 : :
256 [ + - ]: 44664 : if (c < 127)
257 : 44664 : res = hexlookup[c];
258 : :
1238 tgl@sss.pgh.pa.us 259 : 44664 : *out = (char) res;
260 : :
261 : 44664 : return (res >= 0);
262 : : }
263 : :
264 : : uint64
1720 michael@paquier.xyz 265 : 16426 : hex_decode(const char *src, size_t len, char *dst)
266 : : {
1238 tgl@sss.pgh.pa.us 267 : 16426 : return hex_decode_safe(src, len, dst, NULL);
268 : : }
269 : :
270 : : static inline uint64
211 nathan@postgresql.or 271 :GNC 72799 : hex_decode_safe_scalar(const char *src, size_t len, char *dst, Node *escontext)
272 : : {
273 : : const char *s,
274 : : *srcend;
275 : : char v1,
276 : : v2,
277 : : *p;
278 : :
1720 michael@paquier.xyz 279 :CBC 72799 : srcend = src + len;
280 : 72799 : s = src;
281 : 72799 : p = dst;
282 [ + + ]: 95341 : while (s < srcend)
283 : : {
284 [ + + + + : 22594 : if (*s == ' ' || *s == '\n' || *s == '\t' || *s == '\r')
+ - - + ]
285 : : {
286 : 240 : s++;
287 : 240 : continue;
288 : : }
1238 tgl@sss.pgh.pa.us 289 [ + + ]: 22354 : if (!get_hex(s, &v1))
1238 tgl@sss.pgh.pa.us 290 [ + - ]:GBC 32 : ereturn(escontext, 0,
291 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
292 : : errmsg("invalid hexadecimal digit: \"%.*s\"",
293 : : pg_mblen_range(s, srcend), s)));
1720 michael@paquier.xyz 294 :CBC 22322 : s++;
295 [ + + ]: 22322 : if (s >= srcend)
1238 tgl@sss.pgh.pa.us 296 [ + + ]: 12 : ereturn(escontext, 0,
297 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
298 : : errmsg("invalid hexadecimal data: odd number of digits")));
299 [ + + ]: 22310 : if (!get_hex(s, &v2))
300 [ + - ]: 8 : ereturn(escontext, 0,
301 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
302 : : errmsg("invalid hexadecimal digit: \"%.*s\"",
303 : : pg_mblen_range(s, srcend), s)));
1720 michael@paquier.xyz 304 : 22302 : s++;
1238 tgl@sss.pgh.pa.us 305 : 22302 : *p++ = (v1 << 4) | v2;
306 : : }
307 : :
1720 michael@paquier.xyz 308 : 72747 : return p - dst;
309 : : }
310 : :
311 : : /*
312 : : * This helper converts each byte to its binary-equivalent nibble by
313 : : * subtraction and combines them to form the return bytes (separated by zero
314 : : * bytes). Returns false if any input bytes are outside the expected ranges of
315 : : * ASCII values. Otherwise, returns true.
316 : : */
317 : : #ifndef USE_NO_SIMD
318 : : static inline bool
211 nathan@postgresql.or 319 :GNC 246388 : hex_decode_simd_helper(const Vector8 src, Vector8 *dst)
320 : : {
321 : : Vector8 sub;
322 : 246388 : Vector8 mask_hi = vector8_interleave_low(vector8_broadcast(0), vector8_broadcast(0x0f));
323 : 246388 : Vector8 mask_lo = vector8_interleave_low(vector8_broadcast(0x0f), vector8_broadcast(0));
324 : : Vector8 tmp;
325 : : bool ret;
326 : :
327 : 246388 : tmp = vector8_gt(vector8_broadcast('9' + 1), src);
328 : 246388 : sub = vector8_and(tmp, vector8_broadcast('0'));
329 : :
330 : 246388 : tmp = vector8_gt(src, vector8_broadcast('A' - 1));
331 : 246388 : tmp = vector8_and(tmp, vector8_broadcast('A' - 10));
332 : 246388 : sub = vector8_add(sub, tmp);
333 : :
334 : 246388 : tmp = vector8_gt(src, vector8_broadcast('a' - 1));
335 : 246388 : tmp = vector8_and(tmp, vector8_broadcast('a' - 'A'));
336 : 246388 : sub = vector8_add(sub, tmp);
337 : :
338 : 246388 : *dst = vector8_issub(src, sub);
339 : 246388 : ret = !vector8_has_ge(*dst, 0x10);
340 : :
341 : 246388 : tmp = vector8_and(*dst, mask_hi);
342 : 246388 : tmp = vector8_shift_right(tmp, 8);
343 : 246388 : *dst = vector8_and(*dst, mask_lo);
344 : 246388 : *dst = vector8_shift_left(*dst, 4);
345 : 246388 : *dst = vector8_or(*dst, tmp);
346 : 246388 : return ret;
347 : : }
348 : : #endif /* ! USE_NO_SIMD */
349 : :
350 : : uint64
351 : 72799 : hex_decode_safe(const char *src, size_t len, char *dst, Node *escontext)
352 : : {
353 : : #ifdef USE_NO_SIMD
354 : : return hex_decode_safe_scalar(src, len, dst, escontext);
355 : : #else
356 : 72799 : const uint64 tail_idx = len & ~(sizeof(Vector8) * 2 - 1);
357 : : uint64 i;
358 : 72799 : bool success = true;
359 : :
360 : : /*
361 : : * We must process 2 vectors at a time since the output will be half the
362 : : * length of the input.
363 : : */
364 [ + + ]: 195993 : for (i = 0; i < tail_idx; i += sizeof(Vector8) * 2)
365 : : {
366 : : Vector8 srcv;
367 : : Vector8 dstv1;
368 : : Vector8 dstv2;
369 : :
370 : 123194 : vector8_load(&srcv, (const uint8 *) &src[i]);
371 : 123194 : success &= hex_decode_simd_helper(srcv, &dstv1);
372 : :
373 : 123194 : vector8_load(&srcv, (const uint8 *) &src[i + sizeof(Vector8)]);
374 : 123194 : success &= hex_decode_simd_helper(srcv, &dstv2);
375 : :
376 : 123194 : vector8_store((uint8 *) &dst[i / 2], vector8_pack_16(dstv1, dstv2));
377 : : }
378 : :
379 : : /*
380 : : * If something didn't look right in the vector path, try again in the
381 : : * scalar path so that we can handle it correctly.
382 : : */
383 [ + + ]: 72799 : if (!success)
384 : 39 : i = 0;
385 : :
386 : 72799 : return i / 2 + hex_decode_safe_scalar(src + i, len - i, dst + i / 2, escontext);
387 : : #endif
388 : : }
389 : :
390 : : static uint64
2219 tgl@sss.pgh.pa.us 391 :CBC 297928 : hex_enc_len(const char *src, size_t srclen)
392 : : {
1720 michael@paquier.xyz 393 : 297928 : return (uint64) srclen << 1;
394 : : }
395 : :
396 : : static uint64
2219 tgl@sss.pgh.pa.us 397 : 16426 : hex_dec_len(const char *src, size_t srclen)
398 : : {
1720 michael@paquier.xyz 399 : 16426 : return (uint64) srclen >> 1;
400 : : }
401 : :
402 : : /*
403 : : * BASE64 and BASE64URL
404 : : */
405 : :
406 : : static const char _base64[] =
407 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
408 : :
409 : : static const char _base64url[] =
410 : : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
411 : :
412 : : static const int8 b64lookup[128] = {
413 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
414 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
415 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
416 : : 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
417 : : -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
418 : : 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
419 : : -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
420 : : 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
421 : : };
422 : :
423 : : /*
424 : : * pg_base64_encode_internal
425 : : *
426 : : * Helper for decoding base64 or base64url. When url is passed as true the
427 : : * input will be encoded using base64url. len bytes in src is encoded into
428 : : * dst.
429 : : */
430 : : static uint64
227 dgustafsson@postgres 431 :GNC 95 : pg_base64_encode_internal(const char *src, size_t len, char *dst, bool url)
432 : : {
433 : : char *p,
9063 bruce@momjian.us 434 :CBC 95 : *lend = dst + 76;
435 : : const char *s,
1720 michael@paquier.xyz 436 : 95 : *end = src + len;
9063 bruce@momjian.us 437 : 95 : int pos = 2;
438 : 95 : uint32 buf = 0;
227 dgustafsson@postgres 439 [ + + ]:GNC 95 : const char *alphabet = url ? _base64url : _base64;
440 : :
9063 bruce@momjian.us 441 :CBC 95 : s = src;
442 : 95 : p = dst;
443 : :
444 [ + + ]: 1010 : while (s < end)
445 : : {
7528 tgl@sss.pgh.pa.us 446 : 915 : buf |= (unsigned char) *s << (pos << 3);
9063 bruce@momjian.us 447 : 915 : pos--;
448 : 915 : s++;
449 : :
450 : : /* write it out */
451 [ + + ]: 915 : if (pos < 0)
452 : : {
227 dgustafsson@postgres 453 :GNC 280 : *p++ = alphabet[(buf >> 18) & 0x3f];
454 : 280 : *p++ = alphabet[(buf >> 12) & 0x3f];
455 : 280 : *p++ = alphabet[(buf >> 6) & 0x3f];
456 : 280 : *p++ = alphabet[buf & 0x3f];
457 : :
9063 bruce@momjian.us 458 :CBC 280 : pos = 2;
459 : 280 : buf = 0;
460 : :
227 dgustafsson@postgres 461 [ + + + + ]:GNC 280 : if (!url && p >= lend)
462 : : {
463 : 10 : *p++ = '\n';
464 : 10 : lend = p + 76;
465 : : }
466 : : }
467 : : }
468 : :
469 : : /* Handle remaining bytes in buf */
9063 bruce@momjian.us 470 [ + + ]:CBC 95 : if (pos != 2)
471 : : {
227 dgustafsson@postgres 472 :GNC 60 : *p++ = alphabet[(buf >> 18) & 0x3f];
473 : 60 : *p++ = alphabet[(buf >> 12) & 0x3f];
474 : :
475 [ + + ]: 60 : if (pos == 0)
476 : : {
477 : 15 : *p++ = alphabet[(buf >> 6) & 0x3f];
478 [ - + ]: 15 : if (!url)
227 dgustafsson@postgres 479 :UNC 0 : *p++ = '=';
480 : : }
227 dgustafsson@postgres 481 [ + + ]:GNC 45 : else if (!url)
482 : : {
483 : 10 : *p++ = '=';
484 : 10 : *p++ = '=';
485 : : }
486 : : }
487 : :
9063 bruce@momjian.us 488 :CBC 95 : return p - dst;
489 : : }
490 : :
491 : : static uint64
227 dgustafsson@postgres 492 :GNC 10 : pg_base64_encode(const char *src, size_t len, char *dst)
493 : : {
494 : 10 : return pg_base64_encode_internal(src, len, dst, false);
495 : : }
496 : :
497 : : static uint64
498 : 85 : pg_base64url_encode(const char *src, size_t len, char *dst)
499 : : {
500 : 85 : return pg_base64_encode_internal(src, len, dst, true);
501 : : }
502 : :
503 : : /*
504 : : * pg_base64_decode_internal
505 : : *
506 : : * Helper for decoding base64 or base64url. When url is passed as true the
507 : : * input will be assumed to be encoded using base64url.
508 : : */
509 : : static uint64
510 : 94 : pg_base64_decode_internal(const char *src, size_t len, char *dst, bool url)
511 : : {
1720 michael@paquier.xyz 512 :CBC 94 : const char *srcend = src + len,
9063 bruce@momjian.us 513 : 94 : *s = src;
7528 tgl@sss.pgh.pa.us 514 : 94 : char *p = dst;
515 : : char c;
9063 bruce@momjian.us 516 : 94 : int b = 0;
517 : 94 : uint32 buf = 0;
518 : 94 : int pos = 0,
519 : 94 : end = 0;
520 : :
521 [ + + ]: 928 : while (s < srcend)
522 : : {
523 : 842 : c = *s++;
524 : :
525 [ + - + - : 842 : if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ + - + ]
526 : 5 : continue;
527 : :
528 : : /* convert base64url to base64 */
227 dgustafsson@postgres 529 [ + + ]:GNC 837 : if (url)
530 : : {
531 [ + + ]: 341 : if (c == '-')
532 : 15 : c = '+';
533 [ + + ]: 326 : else if (c == '_')
534 : 10 : c = '/';
535 : : }
536 : :
9063 bruce@momjian.us 537 [ + + ]:CBC 837 : if (c == '=')
538 : : {
539 : : /* end sequence */
540 [ + + ]: 26 : if (!end)
541 : : {
542 [ + + ]: 16 : if (pos == 2)
543 : 10 : end = 1;
544 [ + + ]: 6 : else if (pos == 3)
545 : 2 : end = 2;
546 : : else
547 : : {
548 : : /* translator: %s is the name of an encoding scheme */
8318 tgl@sss.pgh.pa.us 549 [ + - + - ]:GBC 4 : ereport(ERROR,
550 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
551 : : errmsg("unexpected \"=\" while decoding %s sequence", url ? "base64url" : "base64")));
552 : : }
553 : : }
9063 bruce@momjian.us 554 :CBC 22 : b = 0;
555 : : }
556 : : else
557 : : {
558 : 811 : b = -1;
559 [ + - + - ]: 811 : if (c > 0 && c < 127)
7528 tgl@sss.pgh.pa.us 560 : 811 : b = b64lookup[(unsigned char) c];
9063 bruce@momjian.us 561 [ + + ]: 811 : if (b < 0)
562 : : {
563 : : /* translator: %s is the name of an encoding scheme */
8318 tgl@sss.pgh.pa.us 564 [ + - + - ]:GBC 4 : ereport(ERROR,
565 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
566 : : errmsg("invalid symbol \"%.*s\" found while decoding %s sequence",
567 : : pg_mblen_range(s - 1, srcend), s - 1,
568 : : url ? "base64url" : "base64")));
569 : : }
570 : : }
571 : : /* add it to buffer */
9063 bruce@momjian.us 572 :CBC 829 : buf = (buf << 6) + b;
573 : 829 : pos++;
574 [ + + ]: 829 : if (pos == 4)
575 : : {
576 : 178 : *p++ = (buf >> 16) & 255;
577 [ + + + + ]: 178 : if (end == 0 || end > 1)
578 : 168 : *p++ = (buf >> 8) & 255;
579 [ + + - + ]: 178 : if (end == 0 || end > 2)
580 : 166 : *p++ = buf & 255;
581 : 178 : buf = 0;
582 : 178 : pos = 0;
583 : : }
584 : : }
585 : :
227 dgustafsson@postgres 586 [ + + ]:GNC 86 : if (pos == 2)
587 : : {
588 : 30 : buf <<= 12;
589 : 30 : *p++ = (buf >> 16) & 0xFF;
590 : : }
591 [ + + ]: 56 : else if (pos == 3)
592 : : {
593 : 15 : buf <<= 6;
594 : 15 : *p++ = (buf >> 16) & 0xFF;
595 : 15 : *p++ = (buf >> 8) & 0xFF;
596 : : }
597 [ + + ]: 41 : else if (pos != 0)
598 : : {
599 : : /* translator: %s is the name of an encoding scheme */
8318 tgl@sss.pgh.pa.us 600 [ + - + - ]:GBC 4 : ereport(ERROR,
601 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
602 : : errmsg("invalid %s end sequence", url ? "base64url" : "base64"),
603 : : errhint("Input data is missing padding, is truncated, or is otherwise corrupted.")));
604 : : }
605 : :
9063 bruce@momjian.us 606 :CBC 82 : return p - dst;
607 : : }
608 : :
609 : : static uint64
227 dgustafsson@postgres 610 :GNC 7 : pg_base64_decode(const char *src, size_t len, char *dst)
611 : : {
612 : 7 : return pg_base64_decode_internal(src, len, dst, false);
613 : : }
614 : :
615 : : static uint64
616 : 87 : pg_base64url_decode(const char *src, size_t len, char *dst)
617 : : {
618 : 87 : return pg_base64_decode_internal(src, len, dst, true);
619 : : }
620 : :
621 : : static uint64
2219 tgl@sss.pgh.pa.us 622 :CBC 10 : pg_base64_enc_len(const char *src, size_t srclen)
623 : : {
624 : : /* 3 bytes will be converted to 4, linefeed after 76 chars */
1062 625 : 10 : return ((uint64) srclen + 2) / 3 * 4 + (uint64) srclen / (76 * 3 / 4);
626 : : }
627 : :
628 : : static uint64
2219 629 : 7 : pg_base64_dec_len(const char *src, size_t srclen)
630 : : {
631 : 7 : return ((uint64) srclen * 3) >> 2;
632 : : }
633 : :
634 : : static uint64
227 dgustafsson@postgres 635 :GNC 85 : pg_base64url_enc_len(const char *src, size_t srclen)
636 : : {
637 : : /*
638 : : * Unlike standard base64, base64url doesn't use padding characters when
639 : : * the input length is not divisible by 3
640 : : */
641 : 85 : return (srclen + 2) / 3 * 4;
642 : : }
643 : :
644 : : static uint64
645 : 87 : pg_base64url_dec_len(const char *src, size_t srclen)
646 : : {
647 : : /*
648 : : * For base64, each 4 characters of input produce at most 3 bytes of
649 : : * output. For base64url without padding, we need to round up to the
650 : : * nearest 4
651 : : */
652 : 87 : size_t adjusted_len = srclen;
653 : :
654 [ + + ]: 87 : if (srclen % 4 != 0)
655 : 49 : adjusted_len += 4 - (srclen % 4);
656 : :
657 : 87 : return (adjusted_len * 3) / 4;
658 : : }
659 : :
660 : : /*
661 : : * Escape
662 : : * Minimally escape bytea to text.
663 : : * De-escape text to bytea.
664 : : *
665 : : * We must escape zero bytes and high-bit-set bytes to avoid generating
666 : : * text that might be invalid in the current encoding, or that might
667 : : * change to something else if passed through an encoding conversion
668 : : * (leading to failing to de-escape to the original bytea value).
669 : : * Also of course backslash itself has to be escaped.
670 : : *
671 : : * De-escaping processes \\ and any \### octal
672 : : */
673 : :
674 : : #define VAL(CH) ((CH) - '0')
675 : : #define DIG(VAL) ((VAL) + '0')
676 : :
677 : : static uint64
1720 michael@paquier.xyz 678 :CBC 44 : esc_encode(const char *src, size_t srclen, char *dst)
679 : : {
7528 tgl@sss.pgh.pa.us 680 : 44 : const char *end = src + srclen;
681 : 44 : char *rp = dst;
2219 682 : 44 : uint64 len = 0;
683 : :
8999 bruce@momjian.us 684 [ + + ]: 420 : while (src < end)
685 : : {
6643 tgl@sss.pgh.pa.us 686 : 376 : unsigned char c = (unsigned char) *src;
687 : :
688 [ + + + + ]: 376 : if (c == '\0' || IS_HIGHBIT_SET(c))
689 : : {
8999 bruce@momjian.us 690 : 69 : rp[0] = '\\';
6643 tgl@sss.pgh.pa.us 691 : 69 : rp[1] = DIG(c >> 6);
692 : 69 : rp[2] = DIG((c >> 3) & 7);
693 : 69 : rp[3] = DIG(c & 7);
8999 bruce@momjian.us 694 : 69 : rp += 4;
695 : 69 : len += 4;
696 : : }
6643 tgl@sss.pgh.pa.us 697 [ - + ]: 307 : else if (c == '\\')
698 : : {
8999 bruce@momjian.us 699 :UBC 0 : rp[0] = '\\';
700 : 0 : rp[1] = '\\';
701 : 0 : rp += 2;
702 : 0 : len += 2;
703 : : }
704 : : else
705 : : {
6643 tgl@sss.pgh.pa.us 706 :CBC 307 : *rp++ = c;
8999 bruce@momjian.us 707 : 307 : len++;
708 : : }
709 : :
710 : 376 : src++;
711 : : }
712 : :
713 : 44 : return len;
714 : : }
715 : :
716 : : static uint64
1720 michael@paquier.xyz 717 : 55 : esc_decode(const char *src, size_t srclen, char *dst)
718 : : {
7528 tgl@sss.pgh.pa.us 719 : 55 : const char *end = src + srclen;
720 : 55 : char *rp = dst;
2219 721 : 55 : uint64 len = 0;
722 : :
8999 bruce@momjian.us 723 [ + + ]: 2012100 : while (src < end)
724 : : {
725 [ + + ]: 2012045 : if (src[0] != '\\')
726 : 2012020 : *rp++ = *src++;
8958 727 [ + - ]: 25 : else if (src + 3 < end &&
728 [ + - + - ]: 25 : (src[1] >= '0' && src[1] <= '3') &&
729 [ + - + - ]: 25 : (src[2] >= '0' && src[2] <= '7') &&
730 [ + - + - ]: 25 : (src[3] >= '0' && src[3] <= '7'))
8999 731 : 25 : {
732 : : int val;
733 : :
734 : 25 : val = VAL(src[1]);
735 : 25 : val <<= 3;
736 : 25 : val += VAL(src[2]);
737 : 25 : val <<= 3;
738 : 25 : *rp++ = val + VAL(src[3]);
739 : 25 : src += 4;
740 : : }
8958 bruce@momjian.us 741 [ # # ]:UBC 0 : else if (src + 1 < end &&
742 [ # # ]: 0 : (src[1] == '\\'))
743 : : {
8999 744 : 0 : *rp++ = '\\';
745 : 0 : src += 2;
746 : : }
747 : : else
748 : : {
749 : : /*
750 : : * One backslash, not followed by ### valid octal. Should never
751 : : * get here, since esc_dec_len does same check.
752 : : */
8318 tgl@sss.pgh.pa.us 753 [ # # ]: 0 : ereport(ERROR,
754 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
755 : : errmsg("invalid input syntax for type %s", "bytea")));
756 : : }
757 : :
8999 bruce@momjian.us 758 :CBC 2012045 : len++;
759 : : }
760 : :
761 : 55 : return len;
762 : : }
763 : :
764 : : static uint64
2219 tgl@sss.pgh.pa.us 765 : 44 : esc_enc_len(const char *src, size_t srclen)
766 : : {
7528 767 : 44 : const char *end = src + srclen;
2219 768 : 44 : uint64 len = 0;
769 : :
8999 bruce@momjian.us 770 [ + + ]: 420 : while (src < end)
771 : : {
6643 tgl@sss.pgh.pa.us 772 [ + + + + ]: 376 : if (*src == '\0' || IS_HIGHBIT_SET(*src))
8999 bruce@momjian.us 773 : 69 : len += 4;
774 [ - + ]: 307 : else if (*src == '\\')
8999 bruce@momjian.us 775 :UBC 0 : len += 2;
776 : : else
8999 bruce@momjian.us 777 :CBC 307 : len++;
778 : :
779 : 376 : src++;
780 : : }
781 : :
782 : 44 : return len;
783 : : }
784 : :
785 : : static uint64
2219 tgl@sss.pgh.pa.us 786 : 55 : esc_dec_len(const char *src, size_t srclen)
787 : : {
7528 788 : 55 : const char *end = src + srclen;
2219 789 : 55 : uint64 len = 0;
790 : :
8999 bruce@momjian.us 791 [ + + ]: 2012100 : while (src < end)
792 : : {
793 [ + + ]: 2012045 : if (src[0] != '\\')
794 : 2012020 : src++;
8958 795 [ + - ]: 25 : else if (src + 3 < end &&
796 [ + - + - ]: 25 : (src[1] >= '0' && src[1] <= '3') &&
797 [ + - + - ]: 25 : (src[2] >= '0' && src[2] <= '7') &&
798 [ + - + - ]: 25 : (src[3] >= '0' && src[3] <= '7'))
799 : : {
800 : : /*
801 : : * backslash + valid octal
802 : : */
8999 803 : 25 : src += 4;
804 : : }
8958 bruce@momjian.us 805 [ # # ]:UBC 0 : else if (src + 1 < end &&
806 [ # # ]: 0 : (src[1] == '\\'))
807 : : {
808 : : /*
809 : : * two backslashes = backslash
810 : : */
8999 811 : 0 : src += 2;
812 : : }
813 : : else
814 : : {
815 : : /*
816 : : * one backslash, not followed by ### valid octal
817 : : */
8318 tgl@sss.pgh.pa.us 818 [ # # ]: 0 : ereport(ERROR,
819 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
820 : : errmsg("invalid input syntax for type %s", "bytea")));
821 : : }
822 : :
8999 bruce@momjian.us 823 :CBC 2012045 : len++;
824 : : }
825 : 55 : return len;
826 : : }
827 : :
828 : : /*
829 : : * BASE32HEX
830 : : */
831 : :
832 : : static const char base32hex_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
833 : :
834 : : static const int8 b32hexlookup[128] = {
835 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
836 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
837 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
838 : : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
839 : : -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
840 : : 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1,
841 : : -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
842 : : 25, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1,
843 : : };
844 : :
845 : : static uint64
41 msawada@postgresql.o 846 :GNC 123 : base32hex_enc_len(const char *src, size_t srclen)
847 : : {
848 : : /* 5 bytes encode to 8 characters, round up to multiple of 8 for padding */
849 : 123 : return ((uint64) srclen + 4) / 5 * 8;
850 : : }
851 : :
852 : : static uint64
853 : 126 : base32hex_dec_len(const char *src, size_t srclen)
854 : : {
855 : : /* Each 8 characters of input produces at most 5 bytes of output */
856 : 126 : return ((uint64) srclen * 5) / 8;
857 : : }
858 : :
859 : : static uint64
860 : 123 : base32hex_encode(const char *src, size_t srclen, char *dst)
861 : : {
862 : 123 : const unsigned char *data = (const unsigned char *) src;
863 : 123 : uint32 bits_buffer = 0;
864 : 123 : int bits_in_buffer = 0;
865 : 123 : uint64 output_pos = 0;
866 : : size_t i;
867 : :
868 [ + + ]: 1636 : for (i = 0; i < srclen; i++)
869 : : {
870 : : /* Add 8 bits to the buffer */
871 : 1513 : bits_buffer = (bits_buffer << 8) | data[i];
872 : 1513 : bits_in_buffer += 8;
873 : :
874 : : /* Extract 5-bit chunks while we have enough bits */
875 [ + + ]: 3868 : while (bits_in_buffer >= 5)
876 : : {
877 : 2355 : bits_in_buffer -= 5;
878 : : /* Extract top 5 bits */
879 : 2355 : dst[output_pos++] = base32hex_table[(bits_buffer >> bits_in_buffer) & 0x1F];
880 : : /* Clear the extracted bits by masking */
881 : 2355 : bits_buffer &= ((1U << bits_in_buffer) - 1);
882 : : }
883 : : }
884 : :
885 : : /* Handle remaining bits (if any) */
886 [ + + ]: 123 : if (bits_in_buffer > 0)
887 : 113 : dst[output_pos++] = base32hex_table[(bits_buffer << (5 - bits_in_buffer)) & 0x1F];
888 : :
889 : : /* Add padding to make length a multiple of 8 (per RFC 4648) */
890 [ + + ]: 751 : while (output_pos % 8 != 0)
891 : 628 : dst[output_pos++] = '=';
892 : :
893 : 123 : return output_pos;
894 : : }
895 : :
896 : : static uint64
897 : 126 : base32hex_decode(const char *src, size_t srclen, char *dst)
898 : : {
899 : 126 : const char *srcend = src + srclen,
900 : 126 : *s = src;
901 : 126 : uint32 bits_buffer = 0;
902 : 126 : int bits_in_buffer = 0;
903 : 126 : uint64 output_pos = 0;
904 : 126 : int pos = 0; /* position within 8-character group (0-7) */
905 : 126 : bool end = false; /* have we seen padding? */
906 : :
907 [ + + ]: 1877 : while (s < srcend)
908 : : {
909 : 1767 : char c = *s++;
910 : : int val;
911 : :
912 : : /* Skip whitespace */
913 [ + - + - : 1767 : if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
+ - - + ]
41 msawada@postgresql.o 914 :UNC 0 : continue;
915 : :
41 msawada@postgresql.o 916 [ + + ]:GNC 1767 : if (c == '=')
917 : : {
918 : : /*
919 : : * The first padding is only valid at positions 2, 4, 5, or 7
920 : : * within an 8-character group (corresponding to 1, 2, 3, or 4
921 : : * input bytes). We only check the position for the first '='
922 : : * character.
923 : : */
924 [ + + ]: 357 : if (!end)
925 : : {
926 [ + + + + : 82 : if (pos != 2 && pos != 4 && pos != 5 && pos != 7)
+ + + + ]
927 [ + - ]: 8 : ereport(ERROR,
928 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
929 : : errmsg("unexpected \"=\" while decoding base32hex sequence")));
930 : 74 : end = true;
931 : : }
932 : 349 : pos++;
933 : 349 : continue;
934 : : }
935 : :
936 : : /* No data characters allowed after padding */
937 [ + + ]: 1410 : if (end)
938 [ + - ]: 4 : ereport(ERROR,
939 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
940 : : errmsg("invalid symbol \"%.*s\" found while decoding base32hex sequence",
941 : : pg_mblen_range(s - 1, srcend), s - 1)));
942 : :
943 : : /* Decode base32hex character (0-9, A-V, case-insensitive) */
944 : 1406 : val = -1;
945 [ + - ]: 1406 : if ((unsigned char) c < 128)
946 : 1406 : val = b32hexlookup[(unsigned char) c];
947 [ + + ]: 1406 : if (val < 0)
948 [ + - ]: 4 : ereport(ERROR,
949 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
950 : : errmsg("invalid symbol \"%.*s\" found while decoding base32hex sequence",
951 : : pg_mblen_range(s - 1, srcend), s - 1)));
952 : :
953 : : /* Add 5 bits to buffer */
954 : 1402 : bits_buffer = (bits_buffer << 5) | val;
955 : 1402 : bits_in_buffer += 5;
956 : 1402 : pos++;
957 : :
958 : : /* Extract 8-bit bytes when we have enough bits */
959 [ + + ]: 2243 : while (bits_in_buffer >= 8)
960 : : {
961 : 841 : bits_in_buffer -= 8;
962 : 841 : dst[output_pos++] = (unsigned char) (bits_buffer >> bits_in_buffer);
963 : : /* Clear the extracted bits */
964 : 841 : bits_buffer &= ((1U << bits_in_buffer) - 1);
965 : : }
966 : :
967 : : /* Reset position after each complete 8-character group */
968 [ + + ]: 1402 : if (pos == 8)
969 : 135 : pos = 0;
970 : : }
971 : :
972 : 110 : return output_pos;
973 : : }
974 : :
975 : : /*
976 : : * Common
977 : : */
978 : :
979 : : static const struct
980 : : {
981 : : const char *name;
982 : : struct pg_encoding enc;
983 : : } enclist[] =
984 : :
985 : : {
986 : : {
987 : : "hex",
988 : : {
989 : : hex_enc_len, hex_dec_len, hex_encode, hex_decode
990 : : }
991 : : },
992 : : {
993 : : "base64",
994 : : {
995 : : pg_base64_enc_len, pg_base64_dec_len, pg_base64_encode, pg_base64_decode
996 : : }
997 : : },
998 : : {
999 : : "base64url",
1000 : : {
1001 : : pg_base64url_enc_len, pg_base64url_dec_len, pg_base64url_encode, pg_base64url_decode
1002 : : }
1003 : : },
1004 : : {
1005 : : "base32hex",
1006 : : {
1007 : : base32hex_enc_len, base32hex_dec_len, base32hex_encode, base32hex_decode
1008 : : }
1009 : : },
1010 : : {
1011 : : "escape",
1012 : : {
1013 : : esc_enc_len, esc_dec_len, esc_encode, esc_decode
1014 : : }
1015 : : },
1016 : : {
1017 : : NULL,
1018 : : {
1019 : : NULL, NULL, NULL, NULL
1020 : : }
1021 : : }
1022 : : };
1023 : :
1024 : : static const struct pg_encoding *
9063 bruce@momjian.us 1025 :CBC 314899 : pg_find_encoding(const char *name)
1026 : : {
1027 : : int i;
1028 : :
1029 [ + + ]: 316443 : for (i = 0; enclist[i].name; i++)
8033 tgl@sss.pgh.pa.us 1030 [ + + ]: 316435 : if (pg_strcasecmp(enclist[i].name, name) == 0)
9063 bruce@momjian.us 1031 : 314891 : return &enclist[i].enc;
1032 : :
9063 bruce@momjian.us 1033 :GBC 8 : return NULL;
1034 : : }
|