base64.c revision 251881
1214077Sgibbs/* 2214077Sgibbs * base64.c: base64 encoding and decoding functions 3214077Sgibbs * 4214077Sgibbs * ==================================================================== 5214077Sgibbs * Licensed to the Apache Software Foundation (ASF) under one 6214077Sgibbs * or more contributor license agreements. See the NOTICE file 7214077Sgibbs * distributed with this work for additional information 8214077Sgibbs * regarding copyright ownership. The ASF licenses this file 9214077Sgibbs * to you under the Apache License, Version 2.0 (the 10214077Sgibbs * "License"); you may not use this file except in compliance 11214077Sgibbs * with the License. You may obtain a copy of the License at 12214077Sgibbs * 13214077Sgibbs * http://www.apache.org/licenses/LICENSE-2.0 14214077Sgibbs * 15214077Sgibbs * Unless required by applicable law or agreed to in writing, 16214077Sgibbs * software distributed under the License is distributed on an 17214077Sgibbs * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18214077Sgibbs * KIND, either express or implied. See the License for the 19214077Sgibbs * specific language governing permissions and limitations 20214077Sgibbs * under the License. 21214077Sgibbs * ==================================================================== 22214077Sgibbs */ 23214077Sgibbs 24214077Sgibbs 25214077Sgibbs 26214077Sgibbs#include <string.h> 27214077Sgibbs 28214077Sgibbs#include <apr.h> 29214077Sgibbs#include <apr_pools.h> 30214077Sgibbs#include <apr_general.h> /* for APR_INLINE */ 31214077Sgibbs 32214077Sgibbs#include "svn_pools.h" 33214077Sgibbs#include "svn_io.h" 34214077Sgibbs#include "svn_error.h" 35214077Sgibbs#include "svn_base64.h" 36214077Sgibbs#include "private/svn_string_private.h" 37214077Sgibbs#include "private/svn_subr_private.h" 38214077Sgibbs 39214077Sgibbs/* When asked to format the base64-encoded output as multiple lines, 40214077Sgibbs we put this many chars in each line (plus one new line char) unless 41214077Sgibbs we run out of data. 42214077Sgibbs It is vital for some of the optimizations below that this value is 43214077Sgibbs a multiple of 4. */ 44214077Sgibbs#define BASE64_LINELEN 76 45214077Sgibbs 46214077Sgibbs/* This number of bytes is encoded in a line of base64 chars. */ 47214077Sgibbs#define BYTES_PER_LINE (BASE64_LINELEN / 4 * 3) 48214077Sgibbs 49214077Sgibbs/* Value -> base64 char mapping table (2^6 entries) */ 50214077Sgibbsstatic const char base64tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ 51214077Sgibbs "abcdefghijklmnopqrstuvwxyz0123456789+/"; 52214077Sgibbs 53214077Sgibbs 54214077Sgibbs/* Binary input --> base64-encoded output */ 55214077Sgibbs 56214077Sgibbsstruct encode_baton { 57214077Sgibbs svn_stream_t *output; 58214077Sgibbs unsigned char buf[3]; /* Bytes waiting to be encoded */ 59214077Sgibbs size_t buflen; /* Number of bytes waiting */ 60214077Sgibbs size_t linelen; /* Bytes output so far on this line */ 61214077Sgibbs apr_pool_t *scratch_pool; 62214077Sgibbs}; 63214077Sgibbs 64214077Sgibbs 65214077Sgibbs/* Base64-encode a group. IN needs to have three bytes and OUT needs 66214077Sgibbs to have room for four bytes. The input group is treated as four 67214077Sgibbs six-bit units which are treated as lookups into base64tab for the 68214077Sgibbs bytes of the output group. */ 69214077Sgibbsstatic APR_INLINE void 70214077Sgibbsencode_group(const unsigned char *in, char *out) 71214077Sgibbs{ 72214077Sgibbs /* Expand input bytes to machine word length (with zero extra cost 73214077Sgibbs on x86/x64) ... */ 74214077Sgibbs apr_size_t part0 = in[0]; 75214077Sgibbs apr_size_t part1 = in[1]; 76214077Sgibbs apr_size_t part2 = in[2]; 77214077Sgibbs 78214077Sgibbs /* ... to prevent these arithmetic operations from being limited to 79214077Sgibbs byte size. This saves non-zero cost conversions of the result when 80214077Sgibbs calculating the addresses within base64tab. */ 81214077Sgibbs out[0] = base64tab[part0 >> 2]; 82214077Sgibbs out[1] = base64tab[((part0 & 3) << 4) | (part1 >> 4)]; 83214077Sgibbs out[2] = base64tab[((part1 & 0xf) << 2) | (part2 >> 6)]; 84214077Sgibbs out[3] = base64tab[part2 & 0x3f]; 85214077Sgibbs} 86214077Sgibbs 87214077Sgibbs/* Base64-encode a line, i.e. BYTES_PER_LINE bytes from DATA into 88214077Sgibbs BASE64_LINELEN chars and append it to STR. It does not assume that 89214077Sgibbs a new line char will be appended, though. 90214077Sgibbs The code in this function will simply transform the data without 91214077Sgibbs performing any boundary checks. Therefore, DATA must have at least 92214077Sgibbs BYTES_PER_LINE left and space for at least another BASE64_LINELEN 93214077Sgibbs chars must have been pre-allocated in STR before calling this 94214077Sgibbs function. */ 95214077Sgibbsstatic void 96214077Sgibbsencode_line(svn_stringbuf_t *str, const char *data) 97214077Sgibbs{ 98214077Sgibbs /* Translate directly from DATA to STR->DATA. */ 99214077Sgibbs const unsigned char *in = (const unsigned char *)data; 100214077Sgibbs char *out = str->data + str->len; 101214077Sgibbs char *end = out + BASE64_LINELEN; 102214077Sgibbs 103214077Sgibbs /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN 104214077Sgibbs a multiple of 4. */ 105214077Sgibbs for ( ; out != end; in += 3, out += 4) 106214077Sgibbs encode_group(in, out); 107214077Sgibbs 108214077Sgibbs /* Expand and terminate the string. */ 109214077Sgibbs *out = '\0'; 110214077Sgibbs str->len += BASE64_LINELEN; 111214077Sgibbs} 112214077Sgibbs 113214077Sgibbs/* (Continue to) Base64-encode the byte string DATA (of length LEN) 114214077Sgibbs into STR. Include newlines every so often if BREAK_LINES is true. 115214077Sgibbs INBUF, INBUFLEN, and LINELEN are used internally; the caller shall 116214077Sgibbs make INBUF have room for three characters and initialize *INBUFLEN 117214077Sgibbs and *LINELEN to 0. 118214077Sgibbs 119214077Sgibbs INBUF and *INBUFLEN carry the leftover data from call to call, and 120214077Sgibbs *LINELEN carries the length of the current output line. */ 121214077Sgibbsstatic void 122214077Sgibbsencode_bytes(svn_stringbuf_t *str, const void *data, apr_size_t len, 123214077Sgibbs unsigned char *inbuf, size_t *inbuflen, size_t *linelen, 124214077Sgibbs svn_boolean_t break_lines) 125214077Sgibbs{ 126214077Sgibbs char group[4]; 127214077Sgibbs const char *p = data, *end = p + len; 128214077Sgibbs apr_size_t buflen; 129214077Sgibbs 130214077Sgibbs /* Resize the stringbuf to make room for the (approximate) size of 131214077Sgibbs output, to avoid repeated resizes later. 132214077Sgibbs Please note that our optimized code relies on the fact that STR 133214077Sgibbs never needs to be resized until we leave this function. */ 134214077Sgibbs buflen = len * 4 / 3 + 4; 135214077Sgibbs if (break_lines) 136214077Sgibbs { 137214077Sgibbs /* Add an extra space for line breaks. */ 138214077Sgibbs buflen += buflen / BASE64_LINELEN; 139214077Sgibbs } 140214077Sgibbs svn_stringbuf_ensure(str, str->len + buflen); 141214077Sgibbs 142214077Sgibbs /* Keep encoding three-byte groups until we run out. */ 143214077Sgibbs while (*inbuflen + (end - p) >= 3) 144214077Sgibbs { 145214077Sgibbs /* May we encode BYTES_PER_LINE bytes without caring about 146 line breaks, data in the temporary INBUF or running out 147 of data? */ 148 if ( *inbuflen == 0 149 && (*linelen == 0 || !break_lines) 150 && (end - p >= BYTES_PER_LINE)) 151 { 152 /* Yes, we can encode a whole chunk of data at once. */ 153 encode_line(str, p); 154 p += BYTES_PER_LINE; 155 *linelen += BASE64_LINELEN; 156 } 157 else 158 { 159 /* No, this is one of a number of special cases. 160 Encode the data byte by byte. */ 161 memcpy(inbuf + *inbuflen, p, 3 - *inbuflen); 162 p += (3 - *inbuflen); 163 encode_group(inbuf, group); 164 svn_stringbuf_appendbytes(str, group, 4); 165 *inbuflen = 0; 166 *linelen += 4; 167 } 168 169 /* Add line breaks as necessary. */ 170 if (break_lines && *linelen == BASE64_LINELEN) 171 { 172 svn_stringbuf_appendbyte(str, '\n'); 173 *linelen = 0; 174 } 175 } 176 177 /* Tack any extra input onto *INBUF. */ 178 memcpy(inbuf + *inbuflen, p, end - p); 179 *inbuflen += (end - p); 180} 181 182 183/* Encode leftover data, if any, and possibly a final newline (if 184 there has been any data and BREAK_LINES is set), appending to STR. 185 LEN must be in the range 0..2. */ 186static void 187encode_partial_group(svn_stringbuf_t *str, const unsigned char *extra, 188 size_t len, size_t linelen, svn_boolean_t break_lines) 189{ 190 unsigned char ingroup[3]; 191 char outgroup[4]; 192 193 if (len > 0) 194 { 195 memcpy(ingroup, extra, len); 196 memset(ingroup + len, 0, 3 - len); 197 encode_group(ingroup, outgroup); 198 memset(outgroup + (len + 1), '=', 4 - (len + 1)); 199 svn_stringbuf_appendbytes(str, outgroup, 4); 200 linelen += 4; 201 } 202 if (break_lines && linelen > 0) 203 svn_stringbuf_appendbyte(str, '\n'); 204} 205 206 207/* Write handler for svn_base64_encode. */ 208static svn_error_t * 209encode_data(void *baton, const char *data, apr_size_t *len) 210{ 211 struct encode_baton *eb = baton; 212 svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool); 213 apr_size_t enclen; 214 svn_error_t *err = SVN_NO_ERROR; 215 216 /* Encode this block of data and write it out. */ 217 encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE); 218 enclen = encoded->len; 219 if (enclen != 0) 220 err = svn_stream_write(eb->output, encoded->data, &enclen); 221 svn_pool_clear(eb->scratch_pool); 222 return err; 223} 224 225 226/* Close handler for svn_base64_encode(). */ 227static svn_error_t * 228finish_encoding_data(void *baton) 229{ 230 struct encode_baton *eb = baton; 231 svn_stringbuf_t *encoded = svn_stringbuf_create_empty(eb->scratch_pool); 232 apr_size_t enclen; 233 svn_error_t *err = SVN_NO_ERROR; 234 235 /* Encode a partial group at the end if necessary, and write it out. */ 236 encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE); 237 enclen = encoded->len; 238 if (enclen != 0) 239 err = svn_stream_write(eb->output, encoded->data, &enclen); 240 241 /* Pass on the close request and clean up the baton. */ 242 if (err == SVN_NO_ERROR) 243 err = svn_stream_close(eb->output); 244 svn_pool_destroy(eb->scratch_pool); 245 return err; 246} 247 248 249svn_stream_t * 250svn_base64_encode(svn_stream_t *output, apr_pool_t *pool) 251{ 252 struct encode_baton *eb = apr_palloc(pool, sizeof(*eb)); 253 svn_stream_t *stream; 254 255 eb->output = output; 256 eb->buflen = 0; 257 eb->linelen = 0; 258 eb->scratch_pool = svn_pool_create(pool); 259 stream = svn_stream_create(eb, pool); 260 svn_stream_set_write(stream, encode_data); 261 svn_stream_set_close(stream, finish_encoding_data); 262 return stream; 263} 264 265 266const svn_string_t * 267svn_base64_encode_string2(const svn_string_t *str, 268 svn_boolean_t break_lines, 269 apr_pool_t *pool) 270{ 271 svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool); 272 unsigned char ingroup[3]; 273 size_t ingrouplen = 0; 274 size_t linelen = 0; 275 276 encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen, 277 break_lines); 278 encode_partial_group(encoded, ingroup, ingrouplen, linelen, 279 break_lines); 280 return svn_stringbuf__morph_into_string(encoded); 281} 282 283const svn_string_t * 284svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool) 285{ 286 return svn_base64_encode_string2(str, TRUE, pool); 287} 288 289 290 291/* Base64-encoded input --> binary output */ 292 293struct decode_baton { 294 svn_stream_t *output; 295 unsigned char buf[4]; /* Bytes waiting to be decoded */ 296 int buflen; /* Number of bytes waiting */ 297 svn_boolean_t done; /* True if we already saw an '=' */ 298 apr_pool_t *scratch_pool; 299}; 300 301 302/* Base64-decode a group. IN needs to have four bytes and OUT needs 303 to have room for three bytes. The input bytes must already have 304 been decoded from base64tab into the range 0..63. The four 305 six-bit values are pasted together to form three eight-bit bytes. */ 306static APR_INLINE void 307decode_group(const unsigned char *in, char *out) 308{ 309 out[0] = (char)((in[0] << 2) | (in[1] >> 4)); 310 out[1] = (char)(((in[1] & 0xf) << 4) | (in[2] >> 2)); 311 out[2] = (char)(((in[2] & 0x3) << 6) | in[3]); 312} 313 314/* Lookup table for base64 characters; reverse_base64[ch] gives a 315 negative value if ch is not a valid base64 character, or otherwise 316 the value of the byte represented; 'A' => 0 etc. */ 317static const signed char reverse_base64[256] = { 318-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 319-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 320-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 32152, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 322-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 32315, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 324-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 32541, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 326-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 327-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 328-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 329-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 330-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 331-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 332-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 333-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 334}; 335 336/* Similar to decode_group but this function also translates the 337 6-bit values from the IN buffer before translating them. 338 Return FALSE if a non-base64 char (e.g. '=' or new line) 339 has been encountered. */ 340static APR_INLINE svn_boolean_t 341decode_group_directly(const unsigned char *in, char *out) 342{ 343 /* Translate the base64 chars in values [0..63, 0xff] */ 344 apr_size_t part0 = (unsigned char)reverse_base64[(unsigned char)in[0]]; 345 apr_size_t part1 = (unsigned char)reverse_base64[(unsigned char)in[1]]; 346 apr_size_t part2 = (unsigned char)reverse_base64[(unsigned char)in[2]]; 347 apr_size_t part3 = (unsigned char)reverse_base64[(unsigned char)in[3]]; 348 349 /* Pack 4x6 bits into 3x8.*/ 350 out[0] = (char)((part0 << 2) | (part1 >> 4)); 351 out[1] = (char)(((part1 & 0xf) << 4) | (part2 >> 2)); 352 out[2] = (char)(((part2 & 0x3) << 6) | part3); 353 354 /* FALSE, iff any part is 0xff. */ 355 return (part0 | part1 | part2 | part3) != (unsigned char)(-1); 356} 357 358/* Base64-encode up to BASE64_LINELEN chars from *DATA and append it to 359 STR. After the function returns, *DATA will point to the first char 360 that has not been translated, yet. Returns TRUE if all BASE64_LINELEN 361 chars could be translated, i.e. no special char has been encountered 362 in between. 363 The code in this function will simply transform the data without 364 performing any boundary checks. Therefore, DATA must have at least 365 BASE64_LINELEN left and space for at least another BYTES_PER_LINE 366 chars must have been pre-allocated in STR before calling this 367 function. */ 368static svn_boolean_t 369decode_line(svn_stringbuf_t *str, const char **data) 370{ 371 /* Decode up to BYTES_PER_LINE bytes directly from *DATA into STR->DATA. */ 372 const unsigned char *p = *(const unsigned char **)data; 373 char *out = str->data + str->len; 374 char *end = out + BYTES_PER_LINE; 375 376 /* We assume that BYTES_PER_LINE is a multiple of 3 and BASE64_LINELEN 377 a multiple of 4. Stop translation as soon as we encounter a special 378 char. Leave the entire group untouched in that case. */ 379 for (; out < end; p += 4, out += 3) 380 if (!decode_group_directly(p, out)) 381 break; 382 383 /* Update string sizes and positions. */ 384 str->len = out - str->data; 385 *out = '\0'; 386 *data = (const char *)p; 387 388 /* Return FALSE, if the caller should continue the decoding process 389 using the slow standard method. */ 390 return out == end; 391} 392 393 394/* (Continue to) Base64-decode the byte string DATA (of length LEN) 395 into STR. INBUF, INBUFLEN, and DONE are used internally; the 396 caller shall have room for four bytes in INBUF and initialize 397 *INBUFLEN to 0 and *DONE to FALSE. 398 399 INBUF and *INBUFLEN carry the leftover bytes from call to call, and 400 *DONE keeps track of whether we've seen an '=' which terminates the 401 encoded data. */ 402static void 403decode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len, 404 unsigned char *inbuf, int *inbuflen, svn_boolean_t *done) 405{ 406 const char *p = data; 407 char group[3]; 408 signed char find; 409 const char *end = data + len; 410 411 /* Resize the stringbuf to make room for the maximum size of output, 412 to avoid repeated resizes later. The optimizations in 413 decode_line rely on no resizes being necessary! 414 415 (*inbuflen+len) is encoded data length 416 (*inbuflen+len)/4 is the number of complete 4-bytes sets 417 (*inbuflen+len)/4*3 is the number of decoded bytes 418 svn_stringbuf_ensure will add an additional byte for the terminating 0. 419 */ 420 svn_stringbuf_ensure(str, str->len + ((*inbuflen + len) / 4) * 3); 421 422 while ( !*done && p < end ) 423 { 424 /* If no data is left in temporary INBUF and there is at least 425 one line-sized chunk left to decode, we may use the optimized 426 code path. */ 427 if ((*inbuflen == 0) && (p + BASE64_LINELEN <= end)) 428 if (decode_line(str, &p)) 429 continue; 430 431 /* A special case or decode_line encountered a special char. */ 432 if (*p == '=') 433 { 434 /* We are at the end and have to decode a partial group. */ 435 if (*inbuflen >= 2) 436 { 437 memset(inbuf + *inbuflen, 0, 4 - *inbuflen); 438 decode_group(inbuf, group); 439 svn_stringbuf_appendbytes(str, group, *inbuflen - 1); 440 } 441 *done = TRUE; 442 } 443 else 444 { 445 find = reverse_base64[(unsigned char)*p]; 446 ++p; 447 448 if (find >= 0) 449 inbuf[(*inbuflen)++] = find; 450 if (*inbuflen == 4) 451 { 452 decode_group(inbuf, group); 453 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