| /* |
| * quoprint.c: quoted-printable encoding and decoding functions |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2002 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| |
| |
| #include <string.h> |
| |
| #include <apr.h> |
| #include <apr_pools.h> |
| #include <apr_general.h> /* for APR_INLINE */ |
| |
| #include "svn_pools.h" |
| #include "svn_io.h" |
| #include "svn_error.h" |
| #include "svn_quoprint.h" |
| |
| |
| /* Caveats: |
| |
| (1) This code is for the encoding and decoding of binary data |
| only. Thus, CRLF sequences are encoded as =0D=0A, and we |
| don't have to worry about tabs and spaces coming before |
| hard newlines, since there aren't any. |
| |
| (2) The decoder does no error reporting, and instead throws |
| away invalid sequences. It also discards CRLF sequences, |
| since those can only appear in the encoding of text data. |
| |
| (3) The decoder does not strip whitespace at the end of a |
| line, so it is not actually compliant with RFC 2045. |
| (Such whitespace should never occur, even in the encoding |
| of text data, but RFC 2045 requires a decoder to detect |
| that a transport agent has added trailing whitespace). |
| |
| (4) The encoder is tailored to make output embeddable in XML, |
| which means it quotes <>'"& as well as the characters |
| required by RFC 2045. */ |
| |
| #define QUOPRINT_LINELEN 76 |
| #define VALID_LITERAL(c) ((c) == '\t' || ((c) >= ' ' && (c) <= '~' \ |
| && (c) != '=')) |
| #define ENCODE_AS_LITERAL(c) (VALID_LITERAL(c) && (c) != '\t' && (c) != '<' \ |
| && (c) != '>' && (c) != '\'' && (c) != '"' \ |
| && (c) != '&') |
| static const char hextab[] = "0123456789ABCDEF"; |
| |
| |
| |
| /* Binary input --> quoted-printable-encoded output */ |
| |
| struct encode_baton { |
| svn_stream_t *output; |
| int linelen; /* Bytes output so far on this line */ |
| apr_pool_t *pool; |
| }; |
| |
| |
| /* Quoted-printable-encode a byte string which may or may not be the |
| totality of the data being encoded. *LINELEN carries the length of |
| the current output line; initialize it to 0. Output will be |
| appended to STR. */ |
| static void |
| encode_bytes (svn_stringbuf_t *str, const char *data, apr_size_t len, |
| int *linelen) |
| { |
| char buf[3]; |
| const char *p; |
| |
| /* Keep encoding three-byte groups until we run out. */ |
| for (p = data; p < data + len; p++) |
| { |
| /* Encode this character. */ |
| if (ENCODE_AS_LITERAL(*p)) |
| { |
| svn_stringbuf_appendbytes (str, p, 1); |
| (*linelen)++; |
| } |
| else |
| { |
| buf[0] = '='; |
| buf[1] = hextab[(*p >> 4) & 0xf]; |
| buf[2] = hextab[*p & 0xf]; |
| svn_stringbuf_appendbytes (str, buf, 3); |
| *linelen += 3; |
| } |
| |
| /* Make sure our output lines don't exceed QUOPRINT_LINELEN. */ |
| if (*linelen + 3 > QUOPRINT_LINELEN) |
| { |
| svn_stringbuf_appendcstr (str, "=\n"); |
| *linelen = 0; |
| } |
| } |
| } |
| |
| |
| /* Write handler for svn_quoprint_encode. */ |
| static svn_error_t * |
| encode_data (void *baton, const char *data, apr_size_t *len) |
| { |
| struct encode_baton *eb = baton; |
| apr_pool_t *subpool = svn_pool_create (eb->pool); |
| svn_stringbuf_t *encoded = svn_stringbuf_create ("", subpool); |
| apr_size_t enclen; |
| svn_error_t *err = SVN_NO_ERROR; |
| |
| /* Encode this block of data and write it out. */ |
| encode_bytes (encoded, data, *len, &eb->linelen); |
| enclen = encoded->len; |
| if (enclen != 0) |
| err = svn_stream_write (eb->output, encoded->data, &enclen); |
| svn_pool_destroy (subpool); |
| return err; |
| } |
| |
| |
| /* Close handler for svn_quoprint_encode(). */ |
| static svn_error_t * |
| finish_encoding_data (void *baton) |
| { |
| struct encode_baton *eb = baton; |
| svn_error_t *err = SVN_NO_ERROR; |
| apr_size_t len; |
| |
| /* Terminate the current output line if it's not empty. */ |
| if (eb->linelen > 0) |
| { |
| len = 2; |
| err = svn_stream_write (eb->output, "=\n", &len); |
| } |
| |
| /* Pass on the close request and clean up the baton. */ |
| if (err == SVN_NO_ERROR) |
| err = svn_stream_close (eb->output); |
| svn_pool_destroy (eb->pool); |
| return err; |
| } |
| |
| |
| svn_stream_t * |
| svn_quoprint_encode (svn_stream_t *output, apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create (pool); |
| struct encode_baton *eb = apr_palloc (subpool, sizeof (*eb)); |
| svn_stream_t *stream; |
| |
| eb->output = output; |
| eb->linelen = 0; |
| eb->pool = subpool; |
| stream = svn_stream_create (eb, pool); |
| svn_stream_set_write (stream, encode_data); |
| svn_stream_set_close (stream, finish_encoding_data); |
| return stream; |
| } |
| |
| |
| svn_stringbuf_t * |
| svn_quoprint_encode_string (svn_stringbuf_t *str, apr_pool_t *pool) |
| { |
| svn_stringbuf_t *encoded = svn_stringbuf_create ("", pool); |
| int linelen = 0; |
| |
| encode_bytes (encoded, str->data, str->len, &linelen); |
| if (linelen > 0) |
| svn_stringbuf_appendcstr (encoded, "=\n"); |
| return encoded; |
| } |
| |
| |
| |
| /* Quoted-printable-encoded input --> binary output */ |
| |
| struct decode_baton { |
| svn_stream_t *output; |
| char buf[3]; /* Bytes waiting to be decoded */ |
| int buflen; /* Number of bytes waiting */ |
| apr_pool_t *pool; |
| }; |
| |
| |
| /* Decode a byte string which may or may not be the total amount of |
| data being decoded. INBUF and *INBUFLEN carry the leftover bytes |
| from call to call. Have room for four bytes in INBUF and |
| initialize *INBUFLEN to 0 and *DONE to FALSE. Output will be |
| appended to STR. */ |
| static void |
| decode_bytes (svn_stringbuf_t *str, const char *data, apr_size_t len, |
| unsigned char *inbuf, int *inbuflen) |
| { |
| const char *p, *find1, *find2; |
| char c; |
| |
| for (p = data; p <= data + len; p++) |
| { |
| /* Append this byte to the buffer and see what we have. */ |
| inbuf[(*inbuflen)++] = *p; |
| if (*inbuf != '=') |
| { |
| /* Literal character; append it if it's valid as such. */ |
| if (VALID_LITERAL(*inbuf)) |
| svn_stringbuf_appendbytes (str, inbuf, 1); |
| *inbuflen = 0; |
| } |
| else if (*inbuf == '=' && *inbuflen == 2 && inbuf[1] == '\n') |
| { |
| /* Soft newline; ignore. */ |
| *inbuflen = 0; |
| } |
| else if (*inbuf == '=' && *inbuflen == 3) |
| { |
| /* Encoded character; decode it and append. */ |
| find1 = strchr(hextab, inbuf[1]); |
| find2 = strchr(hextab, inbuf[2]); |
| if (find1 != NULL && find2 != NULL) |
| { |
| c = ((find1 - hextab) << 4) | (find2 - hextab); |
| svn_stringbuf_appendbytes (str, &c, 1); |
| } |
| *inbuflen = 0; |
| } |
| } |
| } |
| |
| |
| /* Write handler for svn_quoprint_decode. */ |
| static svn_error_t * |
| decode_data (void *baton, const char *data, apr_size_t *len) |
| { |
| struct decode_baton *db = baton; |
| apr_pool_t *subpool; |
| svn_stringbuf_t *decoded; |
| apr_size_t declen; |
| svn_error_t *err = SVN_NO_ERROR; |
| |
| /* Decode this block of data. */ |
| subpool = svn_pool_create (db->pool); |
| decoded = svn_stringbuf_create ("", subpool); |
| decode_bytes (decoded, data, *len, db->buf, &db->buflen); |
| |
| /* Write the output, clean up, go home. */ |
| declen = decoded->len; |
| if (declen != 0) |
| err = svn_stream_write (db->output, decoded->data, &declen); |
| svn_pool_destroy (subpool); |
| return err; |
| } |
| |
| |
| /* Close handler for svn_quoprint_decode(). */ |
| static svn_error_t * |
| finish_decoding_data (void *baton) |
| { |
| struct decode_baton *db = baton; |
| svn_error_t *err; |
| |
| /* Pass on the close request and clean up the baton. */ |
| err = svn_stream_close (db->output); |
| svn_pool_destroy (db->pool); |
| return err; |
| } |
| |
| |
| svn_stream_t * |
| svn_quoprint_decode (svn_stream_t *output, apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create (pool); |
| struct decode_baton *db = apr_palloc (subpool, sizeof (*db)); |
| svn_stream_t *stream; |
| |
| db->output = output; |
| db->buflen = 0; |
| db->pool = subpool; |
| stream = svn_stream_create (db, pool); |
| svn_stream_set_write (stream, decode_data); |
| svn_stream_set_close (stream, finish_decoding_data); |
| return stream; |
| } |
| |
| |
| svn_stringbuf_t * |
| svn_quoprint_decode_string (svn_stringbuf_t *str, apr_pool_t *pool) |
| { |
| svn_stringbuf_t *decoded = svn_stringbuf_create ("", pool); |
| unsigned char ingroup[4]; |
| int ingrouplen = 0; |
| |
| decode_bytes (decoded, str->data, str->len, ingroup, &ingrouplen); |
| return decoded; |
| } |
| |
| |
| |
| /* -------------------------------------------------------------- |
| * local variables: |
| * eval: (load-file "../../tools/dev/svn-dev.el") |
| * end: |
| */ |