Changes to make use of code taken from libassuan. This replaces the
[gnupg.git] / util / assuan-buffer.c
1 /* assuan-buffer.c - read and send data
2  *      Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
3  *
4  * This file is part of Assuan.
5  *
6  * Assuan is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as
8  * published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * Assuan is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 
19  */
20
21 /* Please note that this is a stripped down and modified version of
22    the orginal Assuan code from libassuan. */
23
24
25 #include <config.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <unistd.h>
31 #include <assert.h>
32 #ifdef HAVE_W32_SYSTEM
33 #include <process.h>
34 #endif
35 #include "assuan-defs.h"
36
37 static int
38 writen (assuan_context_t ctx, const char *buffer, size_t length)
39 {
40   while (length)
41     {
42       ssize_t nwritten = ctx->io->writefnc (ctx, buffer, length);
43       
44       if (nwritten < 0)
45         {
46           if (errno == EINTR)
47             continue;
48           return -1; /* write error */
49         }
50       length -= nwritten;
51       buffer += nwritten;
52     }
53   return 0;  /* okay */
54 }
55
56 /* Read an entire line.  */
57 static int
58 readaline (assuan_context_t ctx, char *buf, size_t buflen,
59           int *r_nread, int *r_eof)
60 {
61   size_t nleft = buflen;
62   char *p;
63
64   *r_eof = 0;
65   *r_nread = 0;
66   while (nleft > 0)
67     {
68       ssize_t n = ctx->io->readfnc (ctx, buf, nleft);
69
70       if (n < 0)
71         {
72           if (errno == EINTR)
73             continue;
74           return -1; /* read error */
75         }
76       else if (!n)
77         {
78           *r_eof = 1;
79           break; /* allow incomplete lines */
80         }
81       p = buf;
82       nleft -= n;
83       buf += n;
84       *r_nread += n;
85
86       p = memrchr (p, '\n', n);
87       if (p)
88         break; /* at least one full line available - that's enough for now */
89     }
90
91   return 0;
92 }
93
94
95 int
96 _assuan_read_line (assuan_context_t ctx)
97 {
98   char *line = ctx->inbound.line;
99   int nread, atticlen;
100   int rc;
101   char *endp = 0;
102
103   if (ctx->inbound.eof)
104     return -1;
105
106   atticlen = ctx->inbound.attic.linelen;
107   if (atticlen)
108     {
109       memcpy (line, ctx->inbound.attic.line, atticlen);
110       ctx->inbound.attic.linelen = 0;
111
112       endp = memchr (line, '\n', atticlen);
113       if (endp)
114         /* Found another line in the attic.  */
115         {
116           rc = 0;
117           nread = atticlen;
118           atticlen = 0;
119         }
120       else
121         /* There is pending data but not a full line.  */
122         {
123           assert (atticlen < LINELENGTH);
124           rc = readaline (ctx, line + atticlen,
125                          LINELENGTH - atticlen, &nread, &ctx->inbound.eof);
126         }
127     }
128   else
129     /* No pending data.  */
130     rc = readaline (ctx, line, LINELENGTH,
131                    &nread, &ctx->inbound.eof);
132   if (rc)
133     {
134       if (ctx->log_fp)
135         fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- [Error: %s]\n",
136                  assuan_get_assuan_log_prefix (),
137                  (unsigned int)getpid (), ctx, strerror (errno));
138       return ASSUAN_Read_Error;
139     }
140   if (!nread)
141     {
142       assert (ctx->inbound.eof);
143       if (ctx->log_fp)
144         fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- [EOF]\n",
145                  assuan_get_assuan_log_prefix (),
146                  (unsigned int)getpid (), ctx);
147       return -1;
148     }
149
150   ctx->inbound.attic.pending = 0;
151   nread += atticlen;
152
153   if (! endp)
154     endp = memchr (line, '\n', nread);
155
156   if (endp)
157     {
158       int n = endp - line + 1;
159       if (n < nread)
160         /* LINE contains more than one line.  We copy it to the attic
161            now as handlers are allowed to modify the passed
162            buffer.  */
163         {
164           int len = nread - n;
165           memcpy (ctx->inbound.attic.line, endp + 1, len);
166           ctx->inbound.attic.pending = memrchr (endp + 1, '\n', len) ? 1 : 0;
167           ctx->inbound.attic.linelen = len;
168         }
169
170       if (endp != line && endp[-1] == '\r')
171         endp --;
172       *endp = 0;
173
174       ctx->inbound.linelen = endp - line;
175       if (ctx->log_fp)
176         {
177           fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- ",
178                    assuan_get_assuan_log_prefix (),
179                    (unsigned int)getpid (), ctx);
180           if (ctx->confidential)
181             fputs ("[Confidential data not shown]", ctx->log_fp);
182           else
183             _assuan_log_print_buffer (ctx->log_fp,
184                                       ctx->inbound.line,
185                                       ctx->inbound.linelen);
186           putc ('\n', ctx->log_fp);
187         }
188       return 0;
189     }
190   else
191     {
192       if (ctx->log_fp)
193         fprintf (ctx->log_fp, "%s[%u.%p] DBG: <- [Invalid line]\n",
194                  assuan_get_assuan_log_prefix (),
195                  (unsigned int)getpid (), ctx);
196       *line = 0;
197       ctx->inbound.linelen = 0;
198       return ctx->inbound.eof ? ASSUAN_Line_Not_Terminated
199         : ASSUAN_Line_Too_Long;
200     }
201 }
202
203
204 /* Read the next line from the client or server and return a pointer
205    in *LINE to a buffer holding the line.  LINELEN is the length of
206    *LINE.  The buffer is valid until the next read operation on it.
207    The caller may modify the buffer.  The buffer is invalid (i.e. must
208    not be used) if an error is returned.
209
210    Returns 0 on success or an assuan error code.
211    See also: assuan_pending_line().
212 */
213 assuan_error_t
214 assuan_read_line (assuan_context_t ctx, char **line, size_t *linelen)
215 {
216   assuan_error_t err;
217
218   if (!ctx)
219     return ASSUAN_Invalid_Value;
220
221   err = _assuan_read_line (ctx);
222   *line = ctx->inbound.line;
223   *linelen = ctx->inbound.linelen;
224   return err;
225 }
226
227
228 /* Return true if a full line is buffered (i.e. an entire line may be
229    read without any I/O).  */
230 int
231 assuan_pending_line (assuan_context_t ctx)
232 {
233   return ctx && ctx->inbound.attic.pending;
234 }
235
236
237 assuan_error_t 
238 _assuan_write_line (assuan_context_t ctx, const char *prefix,
239                     const char *line, size_t len)
240 {
241   int rc = 0;
242   size_t prefixlen = prefix? strlen (prefix):0;
243
244   /* Make sure that the line is short enough. */
245   if (len + prefixlen + 2 > ASSUAN_LINELENGTH)
246     {
247       if (ctx->log_fp)
248         fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> "
249                  "[supplied line too long -truncated]\n",
250                  assuan_get_assuan_log_prefix (),
251                  (unsigned int)getpid (), ctx);
252       if (prefixlen > 5)
253         prefixlen = 5;
254       if (len > ASSUAN_LINELENGTH - prefixlen - 2)
255         len = ASSUAN_LINELENGTH - prefixlen - 2 - 1;
256     }
257
258   /* Fixme: we should do some kind of line buffering.  */
259   if (ctx->log_fp)
260     {
261       fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> ",
262                assuan_get_assuan_log_prefix (),
263                (unsigned int)getpid (), ctx);
264       if (ctx->confidential)
265         fputs ("[Confidential data not shown]", ctx->log_fp);
266       else
267         _assuan_log_print_buffer (ctx->log_fp, line, len);
268       putc ('\n', ctx->log_fp);
269     }
270
271   if (prefixlen)
272     {
273       rc = writen (ctx, prefix, prefixlen);
274       if (rc)
275         rc = ASSUAN_Write_Error;
276     }
277   if (!rc)
278     {
279       rc = writen (ctx, line, len);
280       if (rc)
281         rc = ASSUAN_Write_Error;
282       if (!rc)
283         {
284           rc = writen (ctx, "\n", 1);
285           if (rc)
286             rc = ASSUAN_Write_Error;
287         }
288     }
289   return rc;
290 }
291
292
293 assuan_error_t 
294 assuan_write_line (assuan_context_t ctx, const char *line)
295 {
296   size_t len;
297   const char *s;
298
299   if (!ctx)
300     return ASSUAN_Invalid_Value;
301
302   /* Make sure that we never take a LF from the user - this might
303      violate the protocol. */
304   s = strchr (line, '\n');
305   len = s? (s-line) : strlen (line);
306
307   if (ctx->log_fp && s)
308     fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> "
309              "[supplied line contained a LF -truncated]\n",
310              assuan_get_assuan_log_prefix (),
311              (unsigned int)getpid (), ctx);
312
313   return _assuan_write_line (ctx, NULL, line, len);
314 }
315
316
317 \f
318 /* Write out the data in buffer as datalines with line wrapping and
319    percent escaping.  This function is used for GNU's custom streams */
320 int
321 _assuan_cookie_write_data (void *cookie, const char *buffer, size_t orig_size)
322 {
323   assuan_context_t ctx = cookie;
324   size_t size = orig_size;
325   char *line;
326   size_t linelen;
327
328   if (ctx->outbound.data.error)
329     return 0;
330
331   line = ctx->outbound.data.line;
332   linelen = ctx->outbound.data.linelen;
333   line += linelen;
334   while (size)
335     {
336       /* insert data line header */
337       if (!linelen)
338         {
339           *line++ = 'D';
340           *line++ = ' ';
341           linelen += 2;
342         }
343       
344       /* copy data, keep some space for the CRLF and to escape one character */
345       while (size && linelen < LINELENGTH-2-2)
346         {
347           if (*buffer == '%' || *buffer == '\r' || *buffer == '\n')
348             {
349               sprintf (line, "%%%02X", *(unsigned char*)buffer);
350               line += 3;
351               linelen += 3;
352               buffer++;
353             }
354           else
355             {
356               *line++ = *buffer++;
357               linelen++;
358             }
359           size--;
360         }
361       
362       if (linelen >= LINELENGTH-2-2)
363         {
364           if (ctx->log_fp)
365             {
366               fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> ",
367                        assuan_get_assuan_log_prefix (),
368                        (unsigned int)getpid (), ctx);
369
370               if (ctx->confidential)
371                 fputs ("[Confidential data not shown]", ctx->log_fp);
372               else 
373                 _assuan_log_print_buffer (ctx->log_fp, 
374                                           ctx->outbound.data.line,
375                                           linelen);
376               putc ('\n', ctx->log_fp);
377             }
378           *line++ = '\n';
379           linelen++;
380           if (writen (ctx, ctx->outbound.data.line, linelen))
381             {
382               ctx->outbound.data.error = ASSUAN_Write_Error;
383               return 0;
384             }
385           line = ctx->outbound.data.line;
386           linelen = 0;
387         }
388     }
389
390   ctx->outbound.data.linelen = linelen;
391   return (int)orig_size;
392 }
393
394
395 /* Write out any buffered data 
396    This function is used for GNU's custom streams */
397 int
398 _assuan_cookie_write_flush (void *cookie)
399 {
400   assuan_context_t ctx = cookie;
401   char *line;
402   size_t linelen;
403
404   if (ctx->outbound.data.error)
405     return 0;
406
407   line = ctx->outbound.data.line;
408   linelen = ctx->outbound.data.linelen;
409   line += linelen;
410   if (linelen)
411     {
412       if (ctx->log_fp)
413         {
414           fprintf (ctx->log_fp, "%s[%u.%p] DBG: -> ",
415                    assuan_get_assuan_log_prefix (),
416                    (unsigned int)getpid (), ctx);
417           if (ctx->confidential)
418             fputs ("[Confidential data not shown]", ctx->log_fp);
419           else
420             _assuan_log_print_buffer (ctx->log_fp,
421                                       ctx->outbound.data.line, linelen);
422           putc ('\n', ctx->log_fp);
423         }
424       *line++ = '\n';
425       linelen++;
426       if (writen (ctx, ctx->outbound.data.line, linelen))
427         {
428           ctx->outbound.data.error = ASSUAN_Write_Error;
429           return 0;
430         }
431       ctx->outbound.data.linelen = 0;
432     }
433   return 0;
434 }
435
436
437 /**
438  * assuan_send_data:
439  * @ctx: An assuan context
440  * @buffer: Data to send or NULL to flush
441  * @length: length of the data to send/
442  * 
443  * This function may be used by the server or the client to send data
444  * lines.  The data will be escaped as required by the Assuan protocol
445  * and may get buffered until a line is full.  To force sending the
446  * data out @buffer may be passed as NULL (in which case @length must
447  * also be 0); however when used by a client this flush operation does
448  * also send the terminating "END" command to terminate the reponse on
449  * a INQUIRE response.  However, when assuan_transact() is used, this
450  * function takes care of sending END itself.
451  * 
452  * Return value: 0 on success or an error code
453  **/
454 \f
455 assuan_error_t
456 assuan_send_data (assuan_context_t ctx, const void *buffer, size_t length)
457 {
458   if (!ctx)
459     return ASSUAN_Invalid_Value;
460   if (!buffer && length)
461     return ASSUAN_Invalid_Value;
462
463   if (!buffer)
464     { /* flush what we have */
465       _assuan_cookie_write_flush (ctx);
466       if (ctx->outbound.data.error)
467         return ctx->outbound.data.error;
468       if (!ctx->is_server)
469         return assuan_write_line (ctx, "END");
470     }
471   else
472     {
473       _assuan_cookie_write_data (ctx, buffer, length);
474       if (ctx->outbound.data.error)
475         return ctx->outbound.data.error;
476     }
477
478   return 0;
479 }
480