BIO: Add BIO_f_prefix(), a text line prefixing filter
[openssl.git] / crypto / bio / bf_prefix.c
1 /*
2  * Copyright 2018-2019 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9
10 #include <stdio.h>
11 #include <string.h>
12 #include <errno.h>
13 #include "bio_local.h"
14
15 static int prefix_write(BIO *b, const char *out, size_t outl,
16                         size_t *numwritten);
17 static int prefix_read(BIO *b, char *buf, size_t size, size_t *numread);
18 static int prefix_puts(BIO *b, const char *str);
19 static int prefix_gets(BIO *b, char *str, int size);
20 static long prefix_ctrl(BIO *b, int cmd, long arg1, void *arg2);
21 static int prefix_create(BIO *b);
22 static int prefix_destroy(BIO *b);
23 static long prefix_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp);
24
25 static const BIO_METHOD prefix_meth = {
26     BIO_TYPE_BUFFER,
27     "prefix",
28     prefix_write,
29     NULL,
30     prefix_read,
31     NULL,
32     prefix_puts,
33     prefix_gets,
34     prefix_ctrl,
35     prefix_create,
36     prefix_destroy,
37     prefix_callback_ctrl,
38 };
39
40 const BIO_METHOD *BIO_f_prefix(void)
41 {
42     return &prefix_meth;
43 }
44
45 typedef struct prefix_ctx_st {
46     char *prefix;              /* Text prefix, given by user */
47     unsigned int indent;       /* Indentation amount, given by user */
48
49     int linestart;             /* flag to indicate we're at the line start */
50 } PREFIX_CTX;
51
52 static int prefix_create(BIO *b)
53 {
54     PREFIX_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
55
56     if (ctx == NULL)
57         return 0;
58
59     ctx->prefix = NULL;
60     ctx->indent = 0;
61     ctx->linestart = 1;
62     BIO_set_data(b, ctx);
63     BIO_set_init(b, 1);
64     return 1;
65 }
66
67 static int prefix_destroy(BIO *b)
68 {
69     PREFIX_CTX *ctx = BIO_get_data(b);
70
71     OPENSSL_free(ctx->prefix);
72     OPENSSL_free(ctx);
73     return 1;
74 }
75
76 static int prefix_read(BIO *b, char *in, size_t size, size_t *numread)
77 {
78     return BIO_read_ex(BIO_next(b), in, size, numread);
79 }
80
81 static int prefix_write(BIO *b, const char *out, size_t outl,
82                         size_t *numwritten)
83 {
84     PREFIX_CTX *ctx = BIO_get_data(b);
85
86     if (ctx == NULL)
87         return 0;
88
89     /*
90      * If no prefix is set or if it's empty, and no indentation amount is set,
91      * we've got nothing to do here
92      */
93     if ((ctx->prefix == NULL || *ctx->prefix == '\0')
94         && ctx->indent == 0) {
95         /*
96          * We do note if what comes next will be a new line, though, so we're
97          * prepared to handle prefix and indentation the next time around.
98          */
99         if (outl > 0)
100             ctx->linestart = (out[outl-1] == '\n');
101         return BIO_write_ex(BIO_next(b), out, outl, numwritten);
102     }
103
104     *numwritten = 0;
105
106     while (outl > 0) {
107         size_t i;
108         char c;
109
110         /*
111          * If we know that we're at the start of the line, output prefix and
112          * indentation.
113          */
114         if (ctx->linestart) {
115             size_t dontcare;
116
117             if (ctx->prefix != NULL
118                 && !BIO_write_ex(BIO_next(b), ctx->prefix, strlen(ctx->prefix),
119                                  &dontcare))
120                 return 0;
121             BIO_printf(BIO_next(b), "%*s", ctx->indent, "");
122             ctx->linestart = 0;
123         }
124
125         /* Now, go look for the next LF, or the end of the string */
126         for (i = 0, c = '\0'; i < outl && (c = out[i]) != '\n'; i++)
127             continue;
128         if (c == '\n')
129             i++;
130
131         /* Output what we found so far */
132         while (i > 0) {
133             size_t num = 0;
134
135             if (!BIO_write_ex(BIO_next(b), out, i, &num))
136                 return 0;
137             out += num;
138             outl -= num;
139             *numwritten += num;
140             i -= num;
141         }
142
143         /* If we found a LF, what follows is a new line, so take note */
144         if (c == '\n')
145             ctx->linestart = 1;
146     }
147
148     return 1;
149 }
150
151 static long prefix_ctrl(BIO *b, int cmd, long num, void *ptr)
152 {
153     long ret = 0;
154     PREFIX_CTX *ctx = BIO_get_data(b);
155
156     if (ctx == NULL)
157         return -1;
158
159     switch (cmd) {
160     case BIO_CTRL_SET_PREFIX:
161         OPENSSL_free(ctx->prefix);
162         if (ptr == NULL) {
163             ctx->prefix = NULL;
164             ret = 1;
165         } else {
166             ctx->prefix = OPENSSL_strdup((const char *)ptr);
167             ret = ctx->prefix != NULL;
168         }
169         break;
170     case BIO_CTRL_SET_INDENT:
171         if (num >= 0) {
172             ctx->indent = (unsigned int)num;
173             ret = 1;
174         }
175         break;
176     case BIO_CTRL_GET_INDENT:
177         ret = (long)ctx->indent;
178         break;
179     default:
180         /* Commands that we intercept before passing them along */
181         switch (cmd) {
182         case BIO_C_FILE_SEEK:
183         case BIO_CTRL_RESET:
184             ctx->linestart = 1;
185             break;
186         }
187         if (BIO_next(b) != NULL)
188             ret = BIO_ctrl(BIO_next(b), cmd, num, ptr);
189         break;
190     }
191     return ret;
192 }
193
194 static long prefix_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
195 {
196     return BIO_callback_ctrl(BIO_next(b), cmd, fp);
197 }
198
199 static int prefix_gets(BIO *b, char *buf, int size)
200 {
201     return BIO_gets(BIO_next(b), buf, size);
202 }
203
204 static int prefix_puts(BIO *b, const char *str)
205 {
206     return BIO_write(b, str, strlen(str));
207 }