1/*
2 * quoprint.c:  quoted-printable encoding and decoding functions
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <string.h>
27
28#include <apr.h>
29#include <apr_pools.h>
30#include <apr_general.h>        /* for APR_INLINE */
31
32#include "svn_pools.h"
33#include "svn_io.h"
34#include "svn_error.h"
35#include "svn_quoprint.h"
36
37
38/* Caveats:
39
40        (1) This code is for the encoding and decoding of binary data
41            only.  Thus, CRLF sequences are encoded as =0D=0A, and we
42            don't have to worry about tabs and spaces coming before
43            hard newlines, since there aren't any.
44
45        (2) The decoder does no error reporting, and instead throws
46            away invalid sequences.  It also discards CRLF sequences,
47            since those can only appear in the encoding of text data.
48
49        (3) The decoder does not strip whitespace at the end of a
50            line, so it is not actually compliant with RFC 2045.
51            (Such whitespace should never occur, even in the encoding
52            of text data, but RFC 2045 requires a decoder to detect
53            that a transport agent has added trailing whitespace).
54
55        (4) The encoder is tailored to make output embeddable in XML,
56            which means it quotes <>'"& as well as the characters
57            required by RFC 2045.  */
58
59#define QUOPRINT_LINELEN 76
60#define VALID_LITERAL(c) ((c) == '\t' || ((c) >= ' ' && (c) <= '~' \
61                                          && (c) != '='))
62#define ENCODE_AS_LITERAL(c) (VALID_LITERAL(c) && (c) != '\t' && (c) != '<' \
63                              && (c) != '>' && (c) != '\'' && (c) != '"' \
64                              && (c) != '&')
65static const char hextab[] = "0123456789ABCDEF";
66
67
68
69/* Binary input --> quoted-printable-encoded output */
70
71struct encode_baton {
72  svn_stream_t *output;
73  int linelen;                  /* Bytes output so far on this line */
74  apr_pool_t *pool;
75};
76
77
78/* Quoted-printable-encode a byte string which may or may not be the
79   totality of the data being encoded.  *LINELEN carries the length of
80   the current output line; initialize it to 0.  Output will be
81   appended to STR.  */
82static void
83encode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
84             int *linelen)
85{
86  char buf[3];
87  const char *p;
88
89  /* Keep encoding three-byte groups until we run out.  */
90  for (p = data; p < data + len; p++)
91    {
92      /* Encode this character.  */
93      if (ENCODE_AS_LITERAL(*p))
94        {
95          svn_stringbuf_appendbyte(str, *p);
96          (*linelen)++;
97        }
98      else
99        {
100          buf[0] = '=';
101          buf[1] = hextab[(*p >> 4) & 0xf];
102          buf[2] = hextab[*p & 0xf];
103          svn_stringbuf_appendbytes(str, buf, 3);
104          *linelen += 3;
105        }
106
107      /* Make sure our output lines don't exceed QUOPRINT_LINELEN.  */
108      if (*linelen + 3 > QUOPRINT_LINELEN)
109        {
110          svn_stringbuf_appendcstr(str, "=\n");
111          *linelen = 0;
112        }
113    }
114}
115
116
117/* Write handler for svn_quoprint_encode.  */
118static svn_error_t *
119encode_data(void *baton, const char *data, apr_size_t *len)
120{
121  struct encode_baton *eb = baton;
122  apr_pool_t *subpool = svn_pool_create(eb->pool);
123  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(subpool);
124  apr_size_t enclen;
125  svn_error_t *err = SVN_NO_ERROR;
126
127  /* Encode this block of data and write it out.  */
128  encode_bytes(encoded, data, *len, &eb->linelen);
129  enclen = encoded->len;
130  if (enclen != 0)
131    err = svn_stream_write(eb->output, encoded->data, &enclen);
132  svn_pool_destroy(subpool);
133  return err;
134}
135
136
137/* Close handler for svn_quoprint_encode().  */
138static svn_error_t *
139finish_encoding_data(void *baton)
140{
141  struct encode_baton *eb = baton;
142  svn_error_t *err = SVN_NO_ERROR;
143  apr_size_t len;
144
145  /* Terminate the current output line if it's not empty.  */
146  if (eb->linelen > 0)
147    {
148      len = 2;
149      err = svn_stream_write(eb->output, "=\n", &len);
150    }
151
152  /* Pass on the close request and clean up the baton.  */
153  if (err == SVN_NO_ERROR)
154    err = svn_stream_close(eb->output);
155  svn_pool_destroy(eb->pool);
156  return err;
157}
158
159
160svn_stream_t *
161svn_quoprint_encode(svn_stream_t *output, apr_pool_t *pool)
162{
163  apr_pool_t *subpool = svn_pool_create(pool);
164  struct encode_baton *eb = apr_palloc(subpool, sizeof(*eb));
165  svn_stream_t *stream;
166
167  eb->output = output;
168  eb->linelen = 0;
169  eb->pool = subpool;
170  stream = svn_stream_create(eb, pool);
171  svn_stream_set_write(stream, encode_data);
172  svn_stream_set_close(stream, finish_encoding_data);
173  return stream;
174}
175
176
177svn_stringbuf_t *
178svn_quoprint_encode_string(const svn_stringbuf_t *str, apr_pool_t *pool)
179{
180  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool);
181  int linelen = 0;
182
183  encode_bytes(encoded, str->data, str->len, &linelen);
184  if (linelen > 0)
185    svn_stringbuf_appendcstr(encoded, "=\n");
186  return encoded;
187}
188
189
190
191/* Quoted-printable-encoded input --> binary output */
192
193struct decode_baton {
194  svn_stream_t *output;
195  char buf[3];                  /* Bytes waiting to be decoded */
196  int buflen;                   /* Number of bytes waiting */
197  apr_pool_t *pool;
198};
199
200
201/* Decode a byte string which may or may not be the total amount of
202   data being decoded.  INBUF and *INBUFLEN carry the leftover bytes
203   from call to call.  Have room for four bytes in INBUF and
204   initialize *INBUFLEN to 0 and *DONE to FALSE.  Output will be
205   appended to STR.  */
206static void
207decode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
208             char *inbuf, int *inbuflen)
209{
210  const char *p, *find1, *find2;
211  char c;
212
213  for (p = data; p <= data + len; p++)
214    {
215      /* Append this byte to the buffer and see what we have.  */
216      inbuf[(*inbuflen)++] = *p;
217      if (*inbuf != '=')
218        {
219          /* Literal character; append it if it's valid as such.  */
220          if (VALID_LITERAL(*inbuf))
221            svn_stringbuf_appendbyte(str, *inbuf);
222          *inbuflen = 0;
223        }
224      else if (*inbuf == '=' && *inbuflen == 2 && inbuf[1] == '\n')
225        {
226          /* Soft newline; ignore.  */
227          *inbuflen = 0;
228        }
229      else if (*inbuf == '=' && *inbuflen == 3)
230        {
231          /* Encoded character; decode it and append.  */
232          find1 = strchr(hextab, inbuf[1]);
233          find2 = strchr(hextab, inbuf[2]);
234          if (find1 != NULL && find2 != NULL)
235            {
236              c = (char)(((find1 - hextab) << 4) | (find2 - hextab));
237              svn_stringbuf_appendbyte(str, c);
238            }
239          *inbuflen = 0;
240        }
241    }
242}
243
244
245/* Write handler for svn_quoprint_decode.  */
246static svn_error_t *
247decode_data(void *baton, const char *data, apr_size_t *len)
248{
249  struct decode_baton *db = baton;
250  apr_pool_t *subpool;
251  svn_stringbuf_t *decoded;
252  apr_size_t declen;
253  svn_error_t *err = SVN_NO_ERROR;
254
255  /* Decode this block of data.  */
256  subpool = svn_pool_create(db->pool);
257  decoded = svn_stringbuf_create_empty(subpool);
258  decode_bytes(decoded, data, *len, db->buf, &db->buflen);
259
260  /* Write the output, clean up, go home.  */
261  declen = decoded->len;
262  if (declen != 0)
263    err = svn_stream_write(db->output, decoded->data, &declen);
264  svn_pool_destroy(subpool);
265  return err;
266}
267
268
269/* Close handler for svn_quoprint_decode().  */
270static svn_error_t *
271finish_decoding_data(void *baton)
272{
273  struct decode_baton *db = baton;
274  svn_error_t *err;
275
276  /* Pass on the close request and clean up the baton.  */
277  err = svn_stream_close(db->output);
278  svn_pool_destroy(db->pool);
279  return err;
280}
281
282
283svn_stream_t *
284svn_quoprint_decode(svn_stream_t *output, apr_pool_t *pool)
285{
286  apr_pool_t *subpool = svn_pool_create(pool);
287  struct decode_baton *db = apr_palloc(subpool, sizeof(*db));
288  svn_stream_t *stream;
289
290  db->output = output;
291  db->buflen = 0;
292  db->pool = subpool;
293  stream = svn_stream_create(db, pool);
294  svn_stream_set_write(stream, decode_data);
295  svn_stream_set_close(stream, finish_decoding_data);
296  return stream;
297}
298
299
300svn_stringbuf_t *
301svn_quoprint_decode_string(const svn_stringbuf_t *str, apr_pool_t *pool)
302{
303  svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool);
304  char ingroup[4];
305  int ingrouplen = 0;
306
307  decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen);
308  return decoded;
309}
310