1251881Speter/*
2251881Speter * quoprint.c:  quoted-printable encoding and decoding functions
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <string.h>
27251881Speter
28251881Speter#include <apr.h>
29251881Speter#include <apr_pools.h>
30251881Speter#include <apr_general.h>        /* for APR_INLINE */
31251881Speter
32251881Speter#include "svn_pools.h"
33251881Speter#include "svn_io.h"
34251881Speter#include "svn_error.h"
35251881Speter#include "svn_quoprint.h"
36251881Speter
37251881Speter
38251881Speter/* Caveats:
39251881Speter
40251881Speter        (1) This code is for the encoding and decoding of binary data
41251881Speter            only.  Thus, CRLF sequences are encoded as =0D=0A, and we
42251881Speter            don't have to worry about tabs and spaces coming before
43251881Speter            hard newlines, since there aren't any.
44251881Speter
45251881Speter        (2) The decoder does no error reporting, and instead throws
46251881Speter            away invalid sequences.  It also discards CRLF sequences,
47251881Speter            since those can only appear in the encoding of text data.
48251881Speter
49251881Speter        (3) The decoder does not strip whitespace at the end of a
50251881Speter            line, so it is not actually compliant with RFC 2045.
51251881Speter            (Such whitespace should never occur, even in the encoding
52251881Speter            of text data, but RFC 2045 requires a decoder to detect
53251881Speter            that a transport agent has added trailing whitespace).
54251881Speter
55251881Speter        (4) The encoder is tailored to make output embeddable in XML,
56251881Speter            which means it quotes <>'"& as well as the characters
57251881Speter            required by RFC 2045.  */
58251881Speter
59251881Speter#define QUOPRINT_LINELEN 76
60251881Speter#define VALID_LITERAL(c) ((c) == '\t' || ((c) >= ' ' && (c) <= '~' \
61251881Speter                                          && (c) != '='))
62251881Speter#define ENCODE_AS_LITERAL(c) (VALID_LITERAL(c) && (c) != '\t' && (c) != '<' \
63251881Speter                              && (c) != '>' && (c) != '\'' && (c) != '"' \
64251881Speter                              && (c) != '&')
65251881Speterstatic const char hextab[] = "0123456789ABCDEF";
66251881Speter
67251881Speter
68251881Speter
69251881Speter/* Binary input --> quoted-printable-encoded output */
70251881Speter
71251881Speterstruct encode_baton {
72251881Speter  svn_stream_t *output;
73251881Speter  int linelen;                  /* Bytes output so far on this line */
74251881Speter  apr_pool_t *pool;
75251881Speter};
76251881Speter
77251881Speter
78251881Speter/* Quoted-printable-encode a byte string which may or may not be the
79251881Speter   totality of the data being encoded.  *LINELEN carries the length of
80251881Speter   the current output line; initialize it to 0.  Output will be
81251881Speter   appended to STR.  */
82251881Speterstatic void
83251881Speterencode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
84251881Speter             int *linelen)
85251881Speter{
86251881Speter  char buf[3];
87251881Speter  const char *p;
88251881Speter
89251881Speter  /* Keep encoding three-byte groups until we run out.  */
90251881Speter  for (p = data; p < data + len; p++)
91251881Speter    {
92251881Speter      /* Encode this character.  */
93251881Speter      if (ENCODE_AS_LITERAL(*p))
94251881Speter        {
95251881Speter          svn_stringbuf_appendbyte(str, *p);
96251881Speter          (*linelen)++;
97251881Speter        }
98251881Speter      else
99251881Speter        {
100251881Speter          buf[0] = '=';
101251881Speter          buf[1] = hextab[(*p >> 4) & 0xf];
102251881Speter          buf[2] = hextab[*p & 0xf];
103251881Speter          svn_stringbuf_appendbytes(str, buf, 3);
104251881Speter          *linelen += 3;
105251881Speter        }
106251881Speter
107251881Speter      /* Make sure our output lines don't exceed QUOPRINT_LINELEN.  */
108251881Speter      if (*linelen + 3 > QUOPRINT_LINELEN)
109251881Speter        {
110251881Speter          svn_stringbuf_appendcstr(str, "=\n");
111251881Speter          *linelen = 0;
112251881Speter        }
113251881Speter    }
114251881Speter}
115251881Speter
116251881Speter
117251881Speter/* Write handler for svn_quoprint_encode.  */
118251881Speterstatic svn_error_t *
119251881Speterencode_data(void *baton, const char *data, apr_size_t *len)
120251881Speter{
121251881Speter  struct encode_baton *eb = baton;
122251881Speter  apr_pool_t *subpool = svn_pool_create(eb->pool);
123251881Speter  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(subpool);
124251881Speter  apr_size_t enclen;
125251881Speter  svn_error_t *err = SVN_NO_ERROR;
126251881Speter
127251881Speter  /* Encode this block of data and write it out.  */
128251881Speter  encode_bytes(encoded, data, *len, &eb->linelen);
129251881Speter  enclen = encoded->len;
130251881Speter  if (enclen != 0)
131251881Speter    err = svn_stream_write(eb->output, encoded->data, &enclen);
132251881Speter  svn_pool_destroy(subpool);
133251881Speter  return err;
134251881Speter}
135251881Speter
136251881Speter
137251881Speter/* Close handler for svn_quoprint_encode().  */
138251881Speterstatic svn_error_t *
139251881Speterfinish_encoding_data(void *baton)
140251881Speter{
141251881Speter  struct encode_baton *eb = baton;
142251881Speter  svn_error_t *err = SVN_NO_ERROR;
143251881Speter  apr_size_t len;
144251881Speter
145251881Speter  /* Terminate the current output line if it's not empty.  */
146251881Speter  if (eb->linelen > 0)
147251881Speter    {
148251881Speter      len = 2;
149251881Speter      err = svn_stream_write(eb->output, "=\n", &len);
150251881Speter    }
151251881Speter
152251881Speter  /* Pass on the close request and clean up the baton.  */
153251881Speter  if (err == SVN_NO_ERROR)
154251881Speter    err = svn_stream_close(eb->output);
155251881Speter  svn_pool_destroy(eb->pool);
156251881Speter  return err;
157251881Speter}
158251881Speter
159251881Speter
160251881Spetersvn_stream_t *
161251881Spetersvn_quoprint_encode(svn_stream_t *output, apr_pool_t *pool)
162251881Speter{
163251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
164251881Speter  struct encode_baton *eb = apr_palloc(subpool, sizeof(*eb));
165251881Speter  svn_stream_t *stream;
166251881Speter
167251881Speter  eb->output = output;
168251881Speter  eb->linelen = 0;
169251881Speter  eb->pool = subpool;
170251881Speter  stream = svn_stream_create(eb, pool);
171251881Speter  svn_stream_set_write(stream, encode_data);
172251881Speter  svn_stream_set_close(stream, finish_encoding_data);
173251881Speter  return stream;
174251881Speter}
175251881Speter
176251881Speter
177251881Spetersvn_stringbuf_t *
178251881Spetersvn_quoprint_encode_string(const svn_stringbuf_t *str, apr_pool_t *pool)
179251881Speter{
180251881Speter  svn_stringbuf_t *encoded = svn_stringbuf_create_empty(pool);
181251881Speter  int linelen = 0;
182251881Speter
183251881Speter  encode_bytes(encoded, str->data, str->len, &linelen);
184251881Speter  if (linelen > 0)
185251881Speter    svn_stringbuf_appendcstr(encoded, "=\n");
186251881Speter  return encoded;
187251881Speter}
188251881Speter
189251881Speter
190251881Speter
191251881Speter/* Quoted-printable-encoded input --> binary output */
192251881Speter
193251881Speterstruct decode_baton {
194251881Speter  svn_stream_t *output;
195251881Speter  char buf[3];                  /* Bytes waiting to be decoded */
196251881Speter  int buflen;                   /* Number of bytes waiting */
197251881Speter  apr_pool_t *pool;
198251881Speter};
199251881Speter
200251881Speter
201251881Speter/* Decode a byte string which may or may not be the total amount of
202251881Speter   data being decoded.  INBUF and *INBUFLEN carry the leftover bytes
203251881Speter   from call to call.  Have room for four bytes in INBUF and
204251881Speter   initialize *INBUFLEN to 0 and *DONE to FALSE.  Output will be
205251881Speter   appended to STR.  */
206251881Speterstatic void
207251881Speterdecode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
208251881Speter             char *inbuf, int *inbuflen)
209251881Speter{
210251881Speter  const char *p, *find1, *find2;
211251881Speter  char c;
212251881Speter
213251881Speter  for (p = data; p <= data + len; p++)
214251881Speter    {
215251881Speter      /* Append this byte to the buffer and see what we have.  */
216251881Speter      inbuf[(*inbuflen)++] = *p;
217251881Speter      if (*inbuf != '=')
218251881Speter        {
219251881Speter          /* Literal character; append it if it's valid as such.  */
220251881Speter          if (VALID_LITERAL(*inbuf))
221251881Speter            svn_stringbuf_appendbyte(str, *inbuf);
222251881Speter          *inbuflen = 0;
223251881Speter        }
224251881Speter      else if (*inbuf == '=' && *inbuflen == 2 && inbuf[1] == '\n')
225251881Speter        {
226251881Speter          /* Soft newline; ignore.  */
227251881Speter          *inbuflen = 0;
228251881Speter        }
229251881Speter      else if (*inbuf == '=' && *inbuflen == 3)
230251881Speter        {
231251881Speter          /* Encoded character; decode it and append.  */
232251881Speter          find1 = strchr(hextab, inbuf[1]);
233251881Speter          find2 = strchr(hextab, inbuf[2]);
234251881Speter          if (find1 != NULL && find2 != NULL)
235251881Speter            {
236251881Speter              c = (char)(((find1 - hextab) << 4) | (find2 - hextab));
237251881Speter              svn_stringbuf_appendbyte(str, c);
238251881Speter            }
239251881Speter          *inbuflen = 0;
240251881Speter        }
241251881Speter    }
242251881Speter}
243251881Speter
244251881Speter
245251881Speter/* Write handler for svn_quoprint_decode.  */
246251881Speterstatic svn_error_t *
247251881Speterdecode_data(void *baton, const char *data, apr_size_t *len)
248251881Speter{
249251881Speter  struct decode_baton *db = baton;
250251881Speter  apr_pool_t *subpool;
251251881Speter  svn_stringbuf_t *decoded;
252251881Speter  apr_size_t declen;
253251881Speter  svn_error_t *err = SVN_NO_ERROR;
254251881Speter
255251881Speter  /* Decode this block of data.  */
256251881Speter  subpool = svn_pool_create(db->pool);
257251881Speter  decoded = svn_stringbuf_create_empty(subpool);
258251881Speter  decode_bytes(decoded, data, *len, db->buf, &db->buflen);
259251881Speter
260251881Speter  /* Write the output, clean up, go home.  */
261251881Speter  declen = decoded->len;
262251881Speter  if (declen != 0)
263251881Speter    err = svn_stream_write(db->output, decoded->data, &declen);
264251881Speter  svn_pool_destroy(subpool);
265251881Speter  return err;
266251881Speter}
267251881Speter
268251881Speter
269251881Speter/* Close handler for svn_quoprint_decode().  */
270251881Speterstatic svn_error_t *
271251881Speterfinish_decoding_data(void *baton)
272251881Speter{
273251881Speter  struct decode_baton *db = baton;
274251881Speter  svn_error_t *err;
275251881Speter
276251881Speter  /* Pass on the close request and clean up the baton.  */
277251881Speter  err = svn_stream_close(db->output);
278251881Speter  svn_pool_destroy(db->pool);
279251881Speter  return err;
280251881Speter}
281251881Speter
282251881Speter
283251881Spetersvn_stream_t *
284251881Spetersvn_quoprint_decode(svn_stream_t *output, apr_pool_t *pool)
285251881Speter{
286251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
287251881Speter  struct decode_baton *db = apr_palloc(subpool, sizeof(*db));
288251881Speter  svn_stream_t *stream;
289251881Speter
290251881Speter  db->output = output;
291251881Speter  db->buflen = 0;
292251881Speter  db->pool = subpool;
293251881Speter  stream = svn_stream_create(db, pool);
294251881Speter  svn_stream_set_write(stream, decode_data);
295251881Speter  svn_stream_set_close(stream, finish_decoding_data);
296251881Speter  return stream;
297251881Speter}
298251881Speter
299251881Speter
300251881Spetersvn_stringbuf_t *
301251881Spetersvn_quoprint_decode_string(const svn_stringbuf_t *str, apr_pool_t *pool)
302251881Speter{
303251881Speter  svn_stringbuf_t *decoded = svn_stringbuf_create_empty(pool);
304251881Speter  char ingroup[4];
305251881Speter  int ingrouplen = 0;
306251881Speter
307251881Speter  decode_bytes(decoded, str->data, str->len, ingroup, &ingrouplen);
308251881Speter  return decoded;
309251881Speter}
310