11590Srgrimes/*
21590Srgrimes * base64.c:  base64 encoding and decoding functions
31590Srgrimes *
41590Srgrimes * ====================================================================
51590Srgrimes *    Licensed to the Apache Software Foundation (ASF) under one
61590Srgrimes *    or more contributor license agreements.  See the NOTICE file
71590Srgrimes *    distributed with this work for additional information
81590Srgrimes *    regarding copyright ownership.  The ASF licenses this file
91590Srgrimes *    to you under the Apache License, Version 2.0 (the
101590Srgrimes *    "License"); you may not use this file except in compliance
111590Srgrimes *    with the License.  You may obtain a copy of the License at
121590Srgrimes *
131590Srgrimes *      http://www.apache.org/licenses/LICENSE-2.0
141590Srgrimes *
151590Srgrimes *    Unless required by applicable law or agreed to in writing,
161590Srgrimes *    software distributed under the License is distributed on an
171590Srgrimes *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
181590Srgrimes *    KIND, either express or implied.  See the License for the
191590Srgrimes *    specific language governing permissions and limitations
201590Srgrimes *    under the License.
211590Srgrimes * ====================================================================
221590Srgrimes */
231590Srgrimes
241590Srgrimes
251590Srgrimes
261590Srgrimes#include <string.h>
271590Srgrimes
281590Srgrimes#include <apr.h>
291590Srgrimes#include <apr_pools.h>
301590Srgrimes#include <apr_general.h>        /* for APR_INLINE */
311590Srgrimes
3260833Sjake#include "svn_pools.h"
3360833Sjake#include "svn_io.h"
341590Srgrimes#include "svn_error.h"
351590Srgrimes#include "svn_base64.h"
361590Srgrimes#include "private/svn_string_private.h"
371590Srgrimes#include "private/svn_subr_private.h"
381590Srgrimes
391590Srgrimes/* When asked to format the base64-encoded output as multiple lines,
401590Srgrimes   we put this many chars in each line (plus one new line char) unless
411590Srgrimes   we run out of data.
421590Srgrimes   It is vital for some of the optimizations below that this value is
431590Srgrimes   a multiple of 4. */
441590Srgrimes#define BASE64_LINELEN 76
451590Srgrimes
461590Srgrimes/* This number of bytes is encoded in a line of base64 chars. */
471590Srgrimes#define BYTES_PER_LINE (BASE64_LINELEN / 4 * 3)
481590Srgrimes
491590Srgrimes/* Value -> base64 char mapping table (2^6 entries) */
501590Srgrimesstatic const char base64tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
5116438Sache                                "abcdefghijklmnopqrstuvwxyz0123456789+/";
521590Srgrimes
531590Srgrimes
541590Srgrimes/* Binary input --> base64-encoded output */
551590Srgrimes
561590Srgrimesstruct encode_baton {
571590Srgrimes  svn_stream_t *output;
581590Srgrimes  unsigned char buf[3];         /* Bytes waiting to be encoded */
591590Srgrimes  size_t buflen;                /* Number of bytes waiting */
6011547Sdg  size_t linelen;               /* Bytes output so far on this line */
611590Srgrimes  apr_pool_t *scratch_pool;
621590Srgrimes};
631590Srgrimes
641590Srgrimes
651590Srgrimes/* Base64-encode a group.  IN needs to have three bytes and OUT needs
661590Srgrimes   to have room for four bytes.  The input group is treated as four
671590Srgrimes   six-bit units which are treated as lookups into base64tab for the
681590Srgrimes   bytes of the output group.  */
691590Srgrimesstatic APR_INLINE void
701590Srgrimesencode_group(const unsigned char *in, char *out)
711590Srgrimes{
721590Srgrimes  /* Expand input bytes to machine word length (with zero extra cost
731590Srgrimes     on x86/x64) ... */
741590Srgrimes  apr_size_t part0 = in[0];
751590Srgrimes  apr_size_t part1 = in[1];
761590Srgrimes  apr_size_t part2 = in[2];
7760833Sjake
7811547Sdg  /* ... to prevent these arithmetic operations from being limited to
7911547Sdg     byte size.  This saves non-zero cost conversions of the result when
8036062Sjb     calculating the addresses within base64tab. */
811590Srgrimes  out[0] = base64tab[part0 >> 2];
8260833Sjake  out[1] = base64tab[((part0 & 3) << 4) | (part1 >> 4)];
8311547Sdg  out[2] = base64tab[((part1 & 0xf) << 2) | (part2 >> 6)];
841590Srgrimes  out[3] = base64tab[part2 & 0x3f];
851590Srgrimes}
861590Srgrimes
871590Srgrimes/* Base64-encode a line, i.e. BYTES_PER_LINE bytes from DATA into
8836434Sdanny   BASE64_LINELEN chars and append it to STR.  It does not assume that
8936434Sdanny   a new line char will be appended, though.
901590Srgrimes   The code in this function will simply transform the data without
911590Srgrimes   performing any boundary checks.  Therefore, DATA must have at least
921590Srgrimes   BYTES_PER_LINE left and space for at least another BASE64_LINELEN
931590Srgrimes   chars must have been pre-allocated in STR before calling this
941590Srgrimes   function. */
9511547Sdgstatic void
961590Srgrimesencode_line(svn_stringbuf_t *str, const char *data)
971590Srgrimes{
9836434Sdanny  /* Translate directly from DATA to STR->DATA. */
9936434Sdanny  const unsigned char *in = (const unsigned char *)data;
10036434Sdanny  char *out = str->data + str->len;
10136434Sdanny  char *end = out + BASE64_LINELEN;
10236434Sdanny
10336434Sdanny  /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN
10436434Sdanny     a multiple of 4. */
10536434Sdanny  for ( ; out != end; in += 3, out += 4)
1061590Srgrimes    encode_group(in, out);
1071590Srgrimes
1081590Srgrimes  /* Expand and terminate the string. */
1091590Srgrimes  *out = '\0';
1101590Srgrimes  str->len += BASE64_LINELEN;
1111590Srgrimes}
1121590Srgrimes
1131590Srgrimes/* (Continue to) Base64-encode the byte string DATA (of length LEN)
1141590Srgrimes   into STR. Include newlines every so often if BREAK_LINES is true.
1151590Srgrimes   INBUF, INBUFLEN, and LINELEN are used internally; the caller shall
11616438Sache   make INBUF have room for three characters and initialize *INBUFLEN
11716438Sache   and *LINELEN to 0.
1181590Srgrimes
11936434Sdanny   INBUF and *INBUFLEN carry the leftover data from call to call, and
1201590Srgrimes   *LINELEN carries the length of the current output line. */
1211590Srgrimesstatic void
1221590Srgrimesencode_bytes(svn_stringbuf_t *str, const void *data, apr_size_t len,
1231590Srgrimes             unsigned char *inbuf, size_t *inbuflen, size_t *linelen,
1241590Srgrimes             svn_boolean_t break_lines)
1251590Srgrimes{
1261590Srgrimes  char group[4];
1271590Srgrimes  const char *p = data, *end = p + len;
1281590Srgrimes  apr_size_t buflen;
1291590Srgrimes
1301590Srgrimes  /* Resize the stringbuf to make room for the (approximate) size of
1311590Srgrimes     output, to avoid repeated resizes later.
1321590Srgrimes     Please note that our optimized code relies on the fact that STR
1331590Srgrimes     never needs to be resized until we leave this function. */
1341590Srgrimes  buflen = len * 4 / 3 + 4;
1351590Srgrimes  if (break_lines)
1361590Srgrimes    {
1371590Srgrimes      /* Add an extra space for line breaks. */
1381590Srgrimes      buflen += buflen / BASE64_LINELEN;
1391590Srgrimes    }
1401590Srgrimes  svn_stringbuf_ensure(str, str->len + buflen);
1411590Srgrimes
1421590Srgrimes  /* Keep encoding three-byte groups until we run out.  */
1431590Srgrimes  while (*inbuflen + (end - p) >= 3)
14436434Sdanny    {
14536434Sdanny      /* May we encode BYTES_PER_LINE bytes without caring about
14636434Sdanny         line breaks, data in the temporary INBUF or running out
1471590Srgrimes         of data? */
1481590Srgrimes      if (   *inbuflen == 0
1491590Srgrimes          && (*linelen == 0 || !break_lines)
15036434Sdanny          && (end - p >= BYTES_PER_LINE))
15136434Sdanny        {
15236434Sdanny          /* Yes, we can encode a whole chunk of data at once. */
1531590Srgrimes          encode_line(str, p);
1541590Srgrimes          p += BYTES_PER_LINE;
15536434Sdanny          *linelen += BASE64_LINELEN;
1561590Srgrimes        }
1571590Srgrimes      else
15836434Sdanny        {
15936434Sdanny          /* No, this is one of a number of special cases.
1601590Srgrimes             Encode the data byte by byte. */
1611590Srgrimes          memcpy(inbuf + *inbuflen, p, 3 - *inbuflen);
1621590Srgrimes          p += (3 - *inbuflen);
1631590Srgrimes          encode_group(inbuf, group);
1641590Srgrimes          svn_stringbuf_appendbytes(str, group, 4);
1651590Srgrimes          *inbuflen = 0;
1661590Srgrimes          *linelen += 4;
1671590Srgrimes        }
1681590Srgrimes
1691590Srgrimes      /* Add line breaks as necessary. */
1701590Srgrimes      if (break_lines && *linelen == BASE64_LINELEN)
1711590Srgrimes        {
1721590Srgrimes          svn_stringbuf_appendbyte(str, '\n');
1731590Srgrimes          *linelen = 0;
1741590Srgrimes        }
1751590Srgrimes    }
1761590Srgrimes
1771590Srgrimes  /* Tack any extra input onto *INBUF.  */
1781590Srgrimes  memcpy(inbuf + *inbuflen, p, end - p);
1791590Srgrimes  *inbuflen += (end - p);
1801590Srgrimes}
1811590Srgrimes
1821590Srgrimes
18319223Sjoerg/* Encode leftover data, if any, and possibly a final newline (if
1841590Srgrimes   there has been any data and BREAK_LINES is set), appending to STR.
18536062Sjb   LEN must be in the range 0..2.  */
18636062Sjbstatic void
1871590Srgrimesencode_partial_group(svn_stringbuf_t *str, const unsigned char *extra,
18816438Sache                     size_t len, size_t linelen, svn_boolean_t break_lines)
18916438Sache{
19016438Sache  unsigned char ingroup[3];
1911590Srgrimes  char outgroup[4];
19211547Sdg
19311547Sdg  if (len > 0)
1941590Srgrimes    {
1951590Srgrimes      memcpy(ingroup, extra, len);
1961590Srgrimes      memset(ingroup + len, 0, 3 - len);
1971590Srgrimes      encode_group(ingroup, outgroup);
1981590Srgrimes      memset(outgroup + (len + 1), '=', 4 - (len + 1));
1991590Srgrimes      svn_stringbuf_appendbytes(str, outgroup, 4);
2001590Srgrimes      linelen += 4;
2011590Srgrimes    }
2021590Srgrimes  if (break_lines && linelen > 0)
2031590Srgrimes    svn_stringbuf_appendbyte(str, '\n');
2041590Srgrimes}
2051590Srgrimes
2061590Srgrimes
2071590Srgrimes/* Write handler for svn_base64_encode.  */
2081590Srgrimesstatic svn_error_t *
2091590Srgrimesencode_data(void *baton, const char *data, apr_size_t *len)
2101590Srgrimes{
2111590Srgrimes  struct encode_baton *eb = baton;
2121590Srgrimes  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool);
21319223Sjoerg  apr_size_t enclen;
21411547Sdg  svn_error_t *err = SVN_NO_ERROR;
21519223Sjoerg
21619223Sjoerg  /* Encode this block of data and write it out.  */
21719223Sjoerg  encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE);
21811547Sdg  enclen = encoded->len;
2191590Srgrimes  if (enclen != 0)
2201590Srgrimes    err = svn_stream_write(eb->output, encoded->data, &enclen);
2211590Srgrimes  svn_pool_clear(eb->scratch_pool);
22211547Sdg  return err;
22316438Sache}
22416438Sache
22520158Sache
2261590Srgrimes/* Close handler for svn_base64_encode().  */
2271590Srgrimesstatic svn_error_t *
2281590Srgrimesfinish_encoding_data(void *baton)
2291590Srgrimes{
2301590Srgrimes  struct encode_baton *eb = baton;
2311590Srgrimes  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool);
2321590Srgrimes  apr_size_t enclen;
2331590Srgrimes  svn_error_t *err = SVN_NO_ERROR;
2341590Srgrimes
2351590Srgrimes  /* Encode a partial group at the end if necessary, and write it out.  */
2361590Srgrimes  encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE);
2371590Srgrimes  enclen = encoded->len;
2381590Srgrimes  if (enclen != 0)
2391590Srgrimes    err = svn_stream_write(eb->output, encoded->data, &enclen);
2401590Srgrimes
2411590Srgrimes  /* Pass on the close request and clean up the baton.  */
24211547Sdg  if (err == SVN_NO_ERROR)
24316438Sache    err = svn_stream_close(eb->output);
24416438Sache  svn_pool_destroy(eb->scratch_pool);
24520158Sache  return err;
24611547Sdg}
24711547Sdg
24811547Sdg
24911547Sdgsvn_stream_t *
2501590Srgrimessvn_base64_encode(svn_stream_t *output, apr_pool_t *pool)
2511590Srgrimes{
2521590Srgrimes  struct encode_baton *eb = apr_palloc(pool, sizeof(*eb));
2531590Srgrimes  svn_stream_t *stream;
2541590Srgrimes
25511547Sdg  eb->output = output;
25611547Sdg  eb->buflen = 0;
25711547Sdg  eb->linelen = 0;
25811547Sdg  eb->scratch_pool = svn_pool_create(pool);
25911547Sdg  stream = svn_stream_create(eb, pool);
26011547Sdg  svn_stream_set_write(stream, encode_data);
26111547Sdg  svn_stream_set_close(stream, finish_encoding_data);
26211547Sdg  return stream;
26311547Sdg}
26411547Sdg
26511547Sdg
26611547Sdgconst svn_string_t *
26711547Sdgsvn_base64_encode_string2(const svn_string_t *str,
26811547Sdg                          svn_boolean_t break_lines,
26911547Sdg                          apr_pool_t *pool)
2701590Srgrimes{
27111547Sdg  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool);
27211547Sdg  unsigned char ingroup[3];
27311547Sdg  size_t ingrouplen = 0;
27411547Sdg  size_t linelen = 0;
27511547Sdg
27611547Sdg  encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen,
27711547Sdg               break_lines);
27811547Sdg  encode_partial_group(encoded, ingroup, ingrouplen, linelen,
27911547Sdg                       break_lines);
28011547Sdg  return svn_stringbuf__morph_into_string(encoded);
28116438Sache}
28216438Sache
28320158Sacheconst svn_string_t *
28411547Sdgsvn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool)
28511547Sdg{
28611547Sdg  return svn_base64_encode_string2(str, TRUE, pool);
28711547Sdg}
28811547Sdg
28911547Sdg
29011547Sdg
29111547Sdg/* Base64-encoded input --> binary output */
29211547Sdg
29311547Sdgstruct decode_baton {
29411547Sdg  svn_stream_t *output;
29516438Sache  unsigned char buf[4];         /* Bytes waiting to be decoded */
29616438Sache  int buflen;                   /* Number of bytes waiting */
29716438Sache  svn_boolean_t done;           /* True if we already saw an '=' */
29816438Sache  apr_pool_t *scratch_pool;
29916438Sache};
30011547Sdg
30136434Sdanny
30236434Sdanny/* Base64-decode a group.  IN needs to have four bytes and OUT needs
30336434Sdanny   to have room for three bytes.  The input bytes must already have
30436434Sdanny   been decoded from base64tab into the range 0..63.  The four
30536434Sdanny   six-bit values are pasted together to form three eight-bit bytes.  */
30636434Sdannystatic APR_INLINE void
30736434Sdannydecode_group(const unsigned char *in, char *out)
30836434Sdanny{
30936434Sdanny  out[0] = (char)((in[0] << 2) | (in[1] >> 4));
31036434Sdanny  out[1] = (char)(((in[1] & 0xf) << 4) | (in[2] >> 2));
31136434Sdanny  out[2] = (char)(((in[2] & 0x3) << 6) | in[3]);
31236434Sdanny}
3131590Srgrimes
31411547Sdg/* Lookup table for base64 characters; reverse_base64[ch] gives a
31511547Sdg   negative value if ch is not a valid base64 character, or otherwise
31611547Sdg   the value of the byte represented; 'A' => 0 etc. */
31711547Sdgstatic const signed char reverse_base64[256] = {
31811547Sdg-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
31911547Sdg-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3201590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
3211590Srgrimes52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
3221590Srgrimes-1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
3231590Srgrimes15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
32416438Sache-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
32535658Ssteve41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
32635658Ssteve-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3271590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3281590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3291590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3301590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3311590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3321590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
3331590Srgrimes-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
33411547Sdg};
3351590Srgrimes
3361590Srgrimes/* Similar to decode_group but this function also translates the
3371590Srgrimes   6-bit values from the IN buffer before translating them.
3381590Srgrimes   Return FALSE if a non-base64 char (e.g. '=' or new line)
3391590Srgrimes   has been encountered. */
3401590Srgrimesstatic APR_INLINE svn_boolean_t
3411590Srgrimesdecode_group_directly(const unsigned char *in, char *out)
3421590Srgrimes{
3431590Srgrimes  /* Translate the base64 chars in values [0..63, 0xff] */
3441590Srgrimes  apr_size_t part0 = (unsigned char)reverse_base64[(unsigned char)in[0]];
3451590Srgrimes  apr_size_t part1 = (unsigned char)reverse_base64[(unsigned char)in[1]];
3461590Srgrimes  apr_size_t part2 = (unsigned char)reverse_base64[(unsigned char)in[2]];
3471590Srgrimes  apr_size_t part3 = (unsigned char)reverse_base64[(unsigned char)in[3]];
3481590Srgrimes
3491590Srgrimes  /* Pack 4x6 bits into 3x8.*/
3501590Srgrimes  out[0] = (char)((part0 << 2) | (part1 >> 4));
3511590Srgrimes  out[1] = (char)(((part1 & 0xf) << 4) | (part2 >> 2));
3521590Srgrimes  out[2] = (char)(((part2 & 0x3) << 6) | part3);
3531590Srgrimes
3541590Srgrimes  /* FALSE, iff any part is 0xff. */
3551590Srgrimes  return (part0 | part1 | part2 | part3) != (unsigned char)(-1);
3561590Srgrimes}
3571590Srgrimes
3581590Srgrimes/* Base64-encode up to BASE64_LINELEN chars from *DATA and append it to
3591590Srgrimes   STR.  After the function returns, *DATA will point to the first char
3601590Srgrimes   that has not been translated, yet.  Returns TRUE if all BASE64_LINELEN
3611590Srgrimes   chars could be translated, i.e. no special char has been encountered
3621590Srgrimes   in between.
3631590Srgrimes   The code in this function will simply transform the data without
3641590Srgrimes   performing any boundary checks.  Therefore, DATA must have at least
3651590Srgrimes   BASE64_LINELEN left and space for at least another BYTES_PER_LINE
3661590Srgrimes   chars must have been pre-allocated in STR before calling this
3671590Srgrimes   function. */
3681590Srgrimesstatic svn_boolean_t
3691590Srgrimesdecode_line(svn_stringbuf_t *str, const char **data)
3701590Srgrimes{
3711590Srgrimes  /* Decode up to BYTES_PER_LINE bytes directly from *DATA into STR->DATA. */
3721590Srgrimes  const unsigned char *p = *(const unsigned char **)data;
3731590Srgrimes  char *out = str->data + str->len;
3741590Srgrimes  char *end = out + BYTES_PER_LINE;
3751590Srgrimes
3761590Srgrimes  /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN
3771590Srgrimes     a multiple of 4.  Stop translation as soon as we encounter a special
3781590Srgrimes     char.  Leave the entire group untouched in that case. */
3791590Srgrimes  for (; out < end; p += 4, out += 3)
3801590Srgrimes    if (!decode_group_directly(p, out))
3811590Srgrimes      break;
3821590Srgrimes
3831590Srgrimes  /* Update string sizes and positions. */
3841590Srgrimes  str->len = out - str->data;
3851590Srgrimes  *out = '\0';
3861590Srgrimes  *data = (const char *)p;
3871590Srgrimes
3881590Srgrimes  /* Return FALSE, if the caller should continue the decoding process
3891590Srgrimes     using the slow standard method. */
3901590Srgrimes  return out == end;
3911590Srgrimes}
3921590Srgrimes
3931590Srgrimes
3941590Srgrimes/* (Continue to) Base64-decode the byte string DATA (of length LEN)
3951590Srgrimes   into STR. INBUF, INBUFLEN, and DONE are used internally; the
3961590Srgrimes   caller shall have room for four bytes in INBUF and initialize
3971590Srgrimes   *INBUFLEN to 0 and *DONE to FALSE.
3981590Srgrimes
3991590Srgrimes   INBUF and *INBUFLEN carry the leftover bytes from call to call, and
4001590Srgrimes   *DONE keeps track of whether we've seen an '=' which terminates the
4011590Srgrimes   encoded data. */
4021590Srgrimesstatic void
4031590Srgrimesdecode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
4041590Srgrimes             unsigned char *inbuf, int *inbuflen, svn_boolean_t *done)
4051590Srgrimes{
4061590Srgrimes  const char *p = data;
4071590Srgrimes  char group[3];
4081590Srgrimes  signed char find;
4091590Srgrimes  const char *end = data + len;
4101590Srgrimes
4111590Srgrimes  /* Resize the stringbuf to make room for the maximum size of output,
4121590Srgrimes     to avoid repeated resizes later.  The optimizations in
4131590Srgrimes     decode_line rely on no resizes being necessary!
4141590Srgrimes
4151590Srgrimes     (*inbuflen+len) is encoded data length
4161590Srgrimes     (*inbuflen+len)/4 is the number of complete 4-bytes sets
4171590Srgrimes     (*inbuflen+len)/4*3 is the number of decoded bytes
4181590Srgrimes     svn_stringbuf_ensure will add an additional byte for the terminating 0.
4191590Srgrimes  */
4201590Srgrimes  svn_stringbuf_ensure(str, str->len + ((*inbuflen + len) / 4) * 3);
4211590Srgrimes
4221590Srgrimes  while ( !*done && p < end )
4231590Srgrimes    {
4241590Srgrimes      /* If no data is left in temporary INBUF and there is at least
4251590Srgrimes         one line-sized chunk left to decode, we may use the optimized
4261590Srgrimes         code path. */
4271590Srgrimes      if ((*inbuflen == 0) && (p + BASE64_LINELEN <= end))
4281590Srgrimes        if (decode_line(str, &p))
4291590Srgrimes          continue;
4301590Srgrimes
4311590Srgrimes      /* A special case or decode_line encountered a special char. */
4321590Srgrimes      if (*p == '=')
4331590Srgrimes        {
4341590Srgrimes          /* We are at the end and have to decode a partial group.  */
4351590Srgrimes          if (*inbuflen >= 2)
4361590Srgrimes            {
4371590Srgrimes              memset(inbuf + *inbuflen, 0, 4 - *inbuflen);
4381590Srgrimes              decode_group(inbuf, group);
4391590Srgrimes              svn_stringbuf_appendbytes(str, group, *inbuflen - 1);
4401590Srgrimes            }
4411590Srgrimes          *done = TRUE;
4421590Srgrimes        }
4431590Srgrimes      else
44416438Sache        {
44516438Sache          find = reverse_base64[(unsigned char)*p];
4461590Srgrimes          ++p;
44716438Sache
44816438Sache          if (find >= 0)
4491590Srgrimes            inbuf[(*inbuflen)++] = find;
4501590Srgrimes          if (*inbuflen == 4)
4511590Srgrimes            {
4521590Srgrimes              decode_group(inbuf, group);
4531590Srgrimes              svn_stringbuf_appendbytes(str, group, 3);
454              *inbuflen = 0;
455            }
456        }
457    }
458}
459
460
461/* Write handler for svn_base64_decode.  */
462static svn_error_t *
463decode_data(void *baton, const char *data, apr_size_t *len)
464{
465  struct decode_baton *db = baton;
466  svn_stringbuf_t *decoded;
467  apr_size_t declen;
468  svn_error_t *err = SVN_NO_ERROR;
469
470  /* Decode this block of data.  */
471  decoded = svn_stringbuf_create_empty(db->scratch_pool);
472  decode_bytes(decoded, data, *len, db->buf, &db->buflen, &db->done);
473
474  /* Write the output, clean up, go home.  */
475  declen = decoded->len;
476  if (declen != 0)
477    err = svn_stream_write(db->output, decoded->data, &declen);
478  svn_pool_clear(db->scratch_pool);
479  return err;
480}
481
482
483/* Close handler for svn_base64_decode().  */
484static svn_error_t *
485finish_decoding_data(void *baton)
486{
487  struct decode_baton *db = baton;
488  svn_error_t *err;
489
490  /* Pass on the close request and clean up the baton.  */
491  err = svn_stream_close(db->output);
492  svn_pool_destroy(db->scratch_pool);
493  return err;
494}
495
496
497svn_stream_t *
498svn_base64_decode(svn_stream_t *output, apr_pool_t *pool)
499{
500  struct decode_baton *db = apr_palloc(pool, sizeof(*db));
501  svn_stream_t *stream;
502
503  db->output = output;
504  db->buflen = 0;
505  db->done = FALSE;
506  db->scratch_pool = svn_pool_create(pool);
507  stream = svn_stream_create(db, pool);
508  svn_stream_set_write(stream, decode_data);
509  svn_stream_set_close(stream, finish_decoding_data);
510  return stream;
511}
512
513
514const svn_string_t *
515svn_base64_decode_string(const svn_string_t *str, apr_pool_t *pool)
516{
517  svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool);
518  unsigned char ingroup[4];
519  int ingrouplen = 0;
520  svn_boolean_t done = FALSE;
521
522  decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen, &done);
523  return svn_stringbuf__morph_into_string(decoded);
524}
525
526
527/* Return a base64-encoded representation of CHECKSUM, allocated in POOL.
528   If CHECKSUM->kind is not recognized, return NULL.
529   ### That 'NULL' claim was in the header file when this was public, but
530   doesn't look true in the implementation.
531
532   ### This is now only used as a new implementation of svn_base64_from_md5();
533   it would probably be safer to revert that to its old implementation. */
534static svn_stringbuf_t *
535base64_from_checksum(const svn_checksum_t *checksum, apr_pool_t *pool)
536{
537  svn_stringbuf_t *checksum_str;
538  unsigned char ingroup[3];
539  size_t ingrouplen = 0;
540  size_t linelen = 0;
541  checksum_str = svn_stringbuf_create_empty(pool);
542
543  encode_bytes(checksum_str, checksum->digest,
544               svn_checksum_size(checksum), ingroup, &ingrouplen,
545               &linelen, TRUE);
546  encode_partial_group(checksum_str, ingroup, ingrouplen, linelen, TRUE);
547
548  /* Our base64-encoding routines append a final newline if any data
549     was created at all, so let's hack that off. */
550  if (checksum_str->len)
551    {
552      checksum_str->len--;
553      checksum_str->data[checksum_str->len] = 0;
554    }
555
556  return checksum_str;
557}
558
559
560svn_stringbuf_t *
561svn_base64_from_md5(unsigned char digest[], apr_pool_t *pool)
562{
563  svn_checksum_t *checksum
564    = svn_checksum__from_digest_md5(digest, pool);
565
566  return base64_from_checksum(checksum, pool);
567}
568