Coverage Report

Created: 2025-07-12 07:23

/src/pango/subprojects/glib/glib/gbase64.c
Line
Count
Source (jump to first uncovered line)
1
/* gbase64.c - Base64 encoding/decoding
2
 *
3
 *  Copyright (C) 2006 Alexander Larsson <alexl@redhat.com>
4
 *  Copyright (C) 2000-2003 Ximian Inc.
5
 *
6
 * SPDX-License-Identifier: LGPL-2.1-or-later
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
20
 *
21
 * This is based on code in camel, written by:
22
 *    Michael Zucchi <notzed@ximian.com>
23
 *    Jeffrey Stedfast <fejj@ximian.com>
24
 */
25
26
#include "config.h"
27
28
#include <string.h>
29
30
#include "gbase64.h"
31
#include "gtestutils.h"
32
#include "glibintl.h"
33
34
static const char base64_alphabet[] =
35
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
36
37
/**
38
 * g_base64_encode_step:
39
 * @in: (array length=len) (element-type guint8): the binary data to encode
40
 * @len: the length of @in
41
 * @break_lines: whether to break long lines
42
 * @out: (out) (array) (element-type guint8): pointer to destination buffer
43
 * @state: (inout): Saved state between steps, initialize to 0
44
 * @save: (inout): Saved state between steps, initialize to 0
45
 *
46
 * Incrementally encode a sequence of binary data into its Base-64 stringified
47
 * representation. By calling this function multiple times you can convert
48
 * data in chunks to avoid having to have the full encoded data in memory.
49
 *
50
 * When all of the data has been converted you must call
51
 * g_base64_encode_close() to flush the saved state.
52
 *
53
 * The output buffer must be large enough to fit all the data that will
54
 * be written to it. Due to the way base64 encodes you will need
55
 * at least: (@len / 3 + 1) * 4 + 4 bytes (+ 4 may be needed in case of
56
 * non-zero state). If you enable line-breaking you will need at least:
57
 * ((@len / 3 + 1) * 4 + 4) / 76 + 1 bytes of extra space.
58
 *
59
 * @break_lines is typically used when putting base64-encoded data in emails.
60
 * It breaks the lines at 76 columns instead of putting all of the text on
61
 * the same line. This avoids problems with long lines in the email system.
62
 * Note however that it breaks the lines with `LF` characters, not
63
 * `CR LF` sequences, so the result cannot be passed directly to SMTP
64
 * or certain other protocols.
65
 *
66
 * Returns: The number of bytes of output that was written
67
 *
68
 * Since: 2.12
69
 */
70
gsize
71
g_base64_encode_step (const guchar *in,
72
                      gsize         len,
73
                      gboolean      break_lines,
74
                      gchar        *out,
75
                      gint         *state,
76
                      gint         *save)
77
0
{
78
0
  char *outptr;
79
0
  const guchar *inptr;
80
81
0
  g_return_val_if_fail (in != NULL || len == 0, 0);
82
0
  g_return_val_if_fail (out != NULL, 0);
83
0
  g_return_val_if_fail (state != NULL, 0);
84
0
  g_return_val_if_fail (save != NULL, 0);
85
86
0
  if (len == 0)
87
0
    return 0;
88
89
0
  inptr = in;
90
0
  outptr = out;
91
92
0
  if (len + ((char *) save) [0] > 2)
93
0
    {
94
0
      const guchar *inend = in+len-2;
95
0
      int c1, c2, c3;
96
0
      int already;
97
98
0
      already = *state;
99
100
0
      switch (((char *) save) [0])
101
0
        {
102
0
        case 1:
103
0
          c1 = ((unsigned char *) save) [1];
104
0
          goto skip1;
105
0
        case 2:
106
0
          c1 = ((unsigned char *) save) [1];
107
0
          c2 = ((unsigned char *) save) [2];
108
0
          goto skip2;
109
0
        }
110
111
      /*
112
       * yes, we jump into the loop, no i'm not going to change it,
113
       * it's beautiful!
114
       */
115
0
      while (inptr < inend)
116
0
        {
117
0
          c1 = *inptr++;
118
0
        skip1:
119
0
          c2 = *inptr++;
120
0
        skip2:
121
0
          c3 = *inptr++;
122
0
          *outptr++ = base64_alphabet [ c1 >> 2 ];
123
0
          *outptr++ = base64_alphabet [ c2 >> 4 |
124
0
                                        ((c1&0x3) << 4) ];
125
0
          *outptr++ = base64_alphabet [ ((c2 &0x0f) << 2) |
126
0
                                        (c3 >> 6) ];
127
0
          *outptr++ = base64_alphabet [ c3 & 0x3f ];
128
          /* this is a bit ugly ... */
129
0
          if (break_lines && (++already) >= 19)
130
0
            {
131
0
              *outptr++ = '\n';
132
0
              already = 0;
133
0
            }
134
0
        }
135
136
0
      ((char *)save)[0] = 0;
137
0
      len = 2 - (inptr - inend);
138
0
      *state = already;
139
0
    }
140
141
0
  g_assert (len == 0 || len == 1 || len == 2);
142
143
0
    {
144
0
      char *saveout;
145
146
      /* points to the slot for the next char to save */
147
0
      saveout = & (((char *)save)[1]) + ((char *)save)[0];
148
149
      /* len can only be 0 1 or 2 */
150
0
      switch(len)
151
0
        {
152
0
        case 2:
153
0
          *saveout++ = *inptr++;
154
0
          G_GNUC_FALLTHROUGH;
155
0
        case 1:
156
0
          *saveout++ = *inptr++;
157
0
        }
158
0
      ((char *)save)[0] += len;
159
0
    }
160
161
0
  return outptr - out;
162
0
}
163
164
/**
165
 * g_base64_encode_close:
166
 * @break_lines: whether to break long lines
167
 * @out: (out) (array) (element-type guint8): pointer to destination buffer
168
 * @state: (inout): Saved state from g_base64_encode_step()
169
 * @save: (inout): Saved state from g_base64_encode_step()
170
 *
171
 * Flush the status from a sequence of calls to g_base64_encode_step().
172
 *
173
 * The output buffer must be large enough to fit all the data that will
174
 * be written to it. It will need up to 4 bytes, or up to 5 bytes if
175
 * line-breaking is enabled.
176
 *
177
 * The @out array will not be automatically nul-terminated.
178
 *
179
 * Returns: The number of bytes of output that was written
180
 *
181
 * Since: 2.12
182
 */
183
gsize
184
g_base64_encode_close (gboolean  break_lines,
185
                       gchar    *out,
186
                       gint     *state,
187
                       gint     *save)
188
0
{
189
0
  int c1, c2;
190
0
  char *outptr = out;
191
192
0
  g_return_val_if_fail (out != NULL, 0);
193
0
  g_return_val_if_fail (state != NULL, 0);
194
0
  g_return_val_if_fail (save != NULL, 0);
195
196
0
  c1 = ((unsigned char *) save) [1];
197
0
  c2 = ((unsigned char *) save) [2];
198
199
0
  switch (((char *) save) [0])
200
0
    {
201
0
    case 2:
202
0
      outptr [2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
203
0
      g_assert (outptr [2] != 0);
204
0
      goto skip;
205
0
    case 1:
206
0
      outptr[2] = '=';
207
0
      c2 = 0;  /* saved state here is not relevant */
208
0
    skip:
209
0
      outptr [0] = base64_alphabet [ c1 >> 2 ];
210
0
      outptr [1] = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 )];
211
0
      outptr [3] = '=';
212
0
      outptr += 4;
213
0
      break;
214
0
    }
215
0
  if (break_lines)
216
0
    *outptr++ = '\n';
217
218
0
  *save = 0;
219
0
  *state = 0;
220
221
0
  return outptr - out;
222
0
}
223
224
/**
225
 * g_base64_encode:
226
 * @data: (array length=len) (element-type guint8) (nullable): the binary data to encode
227
 * @len: the length of @data
228
 *
229
 * Encode a sequence of binary data into its Base-64 stringified
230
 * representation.
231
 *
232
 * Returns: (transfer full): a newly allocated, zero-terminated Base-64
233
 *               encoded string representing @data. The returned string must
234
 *               be freed with g_free().
235
 *
236
 * Since: 2.12
237
 */
238
gchar *
239
g_base64_encode (const guchar *data,
240
                 gsize         len)
241
0
{
242
0
  gchar *out;
243
0
  gint state = 0, outlen;
244
0
  gint save = 0;
245
246
0
  g_return_val_if_fail (data != NULL || len == 0, NULL);
247
248
  /* We can use a smaller limit here, since we know the saved state is 0,
249
     +1 is needed for trailing \0, also check for unlikely integer overflow */
250
0
  g_return_val_if_fail (len < ((G_MAXSIZE - 1) / 4 - 1) * 3, NULL);
251
252
0
  out = g_malloc ((len / 3 + 1) * 4 + 1);
253
254
0
  outlen = g_base64_encode_step (data, len, FALSE, out, &state, &save);
255
0
  outlen += g_base64_encode_close (FALSE, out + outlen, &state, &save);
256
0
  out[outlen] = '\0';
257
258
0
  return (gchar *) out;
259
0
}
260
261
static const unsigned char mime_base64_rank[256] = {
262
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
263
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
264
  255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
265
   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,
266
  255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
267
   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
268
  255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
269
   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
270
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
271
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
272
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
273
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
274
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
275
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
276
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
277
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
278
};
279
280
/**
281
 * g_base64_decode_step: (skip)
282
 * @in: (array length=len) (element-type guint8): binary input data
283
 * @len: max length of @in data to decode
284
 * @out: (out caller-allocates) (array) (element-type guint8): output buffer
285
 * @state: (inout): Saved state between steps, initialize to 0
286
 * @save: (inout): Saved state between steps, initialize to 0
287
 *
288
 * Incrementally decode a sequence of binary data from its Base-64 stringified
289
 * representation. By calling this function multiple times you can convert
290
 * data in chunks to avoid having to have the full encoded data in memory.
291
 *
292
 * The output buffer must be large enough to fit all the data that will
293
 * be written to it. Since base64 encodes 3 bytes in 4 chars you need
294
 * at least: (@len / 4) * 3 + 3 bytes (+ 3 may be needed in case of non-zero
295
 * state).
296
 *
297
 * Returns: The number of bytes of output that was written
298
 *
299
 * Since: 2.12
300
 **/
301
gsize
302
g_base64_decode_step (const gchar  *in,
303
                      gsize         len,
304
                      guchar       *out,
305
                      gint         *state,
306
                      guint        *save)
307
0
{
308
0
  const guchar *inptr;
309
0
  guchar *outptr;
310
0
  const guchar *inend;
311
0
  guchar c, rank;
312
0
  guchar last[2];
313
0
  unsigned int v;
314
0
  int i;
315
316
0
  g_return_val_if_fail (in != NULL || len == 0, 0);
317
0
  g_return_val_if_fail (out != NULL, 0);
318
0
  g_return_val_if_fail (state != NULL, 0);
319
0
  g_return_val_if_fail (save != NULL, 0);
320
321
0
  if (len == 0)
322
0
    return 0;
323
324
0
  inend = (const guchar *)in+len;
325
0
  outptr = out;
326
327
  /* convert 4 base64 bytes to 3 normal bytes */
328
0
  v=*save;
329
0
  i=*state;
330
331
0
  last[0] = last[1] = 0;
332
333
  /* we use the sign in the state to determine if we got a padding character
334
     in the previous sequence */
335
0
  if (i < 0)
336
0
    {
337
0
      i = -i;
338
0
      last[0] = '=';
339
0
    }
340
341
0
  inptr = (const guchar *)in;
342
0
  while (inptr < inend)
343
0
    {
344
0
      c = *inptr++;
345
0
      rank = mime_base64_rank [c];
346
0
      if (rank != 0xff)
347
0
        {
348
0
          last[1] = last[0];
349
0
          last[0] = c;
350
0
          v = (v<<6) | rank;
351
0
          i++;
352
0
          if (i==4)
353
0
            {
354
0
              *outptr++ = v>>16;
355
0
              if (last[1] != '=')
356
0
                *outptr++ = v>>8;
357
0
              if (last[0] != '=')
358
0
                *outptr++ = v;
359
0
              i=0;
360
0
            }
361
0
        }
362
0
    }
363
364
0
  *save = v;
365
0
  *state = last[0] == '=' ? -i : i;
366
367
0
  return outptr - out;
368
0
}
369
370
/**
371
 * g_base64_decode:
372
 * @text: (not nullable): zero-terminated string with base64 text to decode
373
 * @out_len: (out): The length of the decoded data is written here
374
 *
375
 * Decode a sequence of Base-64 encoded text into binary data.  Note
376
 * that the returned binary data is not necessarily zero-terminated,
377
 * so it should not be used as a character string.
378
 *
379
 * Returns: (transfer full) (array length=out_len) (element-type guint8):
380
 *               newly allocated buffer containing the binary data
381
 *               that @text represents. The returned buffer must
382
 *               be freed with g_free().
383
 *
384
 * Since: 2.12
385
 */
386
guchar *
387
g_base64_decode (const gchar *text,
388
                 gsize       *out_len)
389
0
{
390
0
  guchar *ret;
391
0
  gsize input_length;
392
0
  gint state = 0;
393
0
  guint save = 0;
394
395
0
  g_return_val_if_fail (text != NULL, NULL);
396
0
  g_return_val_if_fail (out_len != NULL, NULL);
397
398
0
  input_length = strlen (text);
399
400
  /* We can use a smaller limit here, since we know the saved state is 0,
401
     +1 used to avoid calling g_malloc0(0), and hence returning NULL */
402
0
  ret = g_malloc0 ((input_length / 4) * 3 + 1);
403
404
0
  *out_len = g_base64_decode_step (text, input_length, ret, &state, &save);
405
406
0
  return ret;
407
0
}
408
409
/**
410
 * g_base64_decode_inplace:
411
 * @text: (inout) (array length=out_len) (element-type guint8): zero-terminated
412
 *        string with base64 text to decode
413
 * @out_len: (inout): The length of the decoded data is written here
414
 *
415
 * Decode a sequence of Base-64 encoded text into binary data
416
 * by overwriting the input data.
417
 *
418
 * Returns: (transfer none): The binary data that @text responds. This pointer
419
 *               is the same as the input @text.
420
 *
421
 * Since: 2.20
422
 */
423
guchar *
424
g_base64_decode_inplace (gchar *text,
425
                         gsize *out_len)
426
0
{
427
0
  gint state = 0;
428
0
  size_t input_length;
429
0
  guint save = 0;
430
431
0
  g_return_val_if_fail (text != NULL, NULL);
432
0
  g_return_val_if_fail (out_len != NULL, NULL);
433
434
0
  input_length = strlen (text);
435
436
0
  g_return_val_if_fail (input_length > 1, NULL);
437
438
0
  *out_len = g_base64_decode_step (text, input_length, (guchar *) text, &state, &save);
439
440
0
  return (guchar *) text;
441
0
}