1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * mod_data.c --- Turn the response into an rfc2397 data URL, suitable for
19  *                displaying as inline content on a page.
20  */
21 
22 #include "apr.h"
23 #include "apr_strings.h"
24 #include "apr_buckets.h"
25 #include "apr_base64.h"
26 #include "apr_lib.h"
27 
28 #include "ap_config.h"
29 #include "util_filter.h"
30 #include "httpd.h"
31 #include "http_config.h"
32 #include "http_log.h"
33 #include "http_request.h"
34 #include "http_protocol.h"
35 
36 #define DATA_FILTER "DATA"
37 
38 module AP_MODULE_DECLARE_DATA data_module;
39 
40 typedef struct data_ctx
41 {
42     unsigned char overflow[3];
43     int count;
44     apr_bucket_brigade *bb;
45 } data_ctx;
46 
47 /**
48  * Create a data URL as follows:
49  *
50  * data:[<mime-type>;][charset=<charset>;]base64,<payload>
51  *
52  * Where:
53  *
54  * mime-type: The mime type of the original response.
55  * charset: The optional character set corresponding to the mime type.
56  * payload: A base64 version of the response body.
57  *
58  * The content type of the response is set to text/plain.
59  *
60  * The Content-Length header, if present, is updated with the new content
61  * length based on the increase in size expected from the base64 conversion.
62  * If the Content-Length header is too large to fit into an int, we remove
63  * the Content-Length header instead.
64  */
data_out_filter(ap_filter_t * f,apr_bucket_brigade * bb)65 static apr_status_t data_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
66 {
67     apr_bucket *e, *ee;
68     request_rec *r = f->r;
69     data_ctx *ctx = f->ctx;
70     apr_status_t rv = APR_SUCCESS;
71 
72     /* first time in? create a context */
73     if (!ctx) {
74         char *type;
75         char *charset = NULL;
76         char *end;
77         const char *content_length;
78 
79         /* base64-ing won't work on subrequests, it would be nice if
80          * it did. Within subrequests, we have no EOS to check for,
81          * so we don't know when to flush the tail to the network.
82          */
83         if (!ap_is_initial_req(f->r)) {
84             ap_remove_output_filter(f);
85             return ap_pass_brigade(f->next, bb);
86         }
87 
88         ctx = f->ctx = apr_pcalloc(r->pool, sizeof(*ctx));
89         ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
90 
91         type = apr_pstrdup(r->pool, r->content_type);
92         if (type) {
93             charset = strchr(type, ' ');
94             if (charset) {
95                 *charset++ = 0;
96                 end = strchr(charset, ' ');
97                 if (end) {
98                     *end++ = 0;
99                 }
100             }
101         }
102 
103         apr_brigade_printf(ctx->bb, NULL, NULL, "data:%s%s;base64,",
104                 type ? type : "", charset ? charset : "");
105 
106         content_length = apr_table_get(r->headers_out, "Content-Length");
107         if (content_length) {
108             apr_off_t len, clen;
109             apr_brigade_length(ctx->bb, 1, &len);
110             if (ap_parse_strict_length(&clen, content_length)
111                     && clen < APR_INT32_MAX) {
112                 ap_set_content_length(r, len +
113                                       apr_base64_encode_len((int)clen) - 1);
114             }
115             else {
116                 apr_table_unset(r->headers_out, "Content-Length");
117             }
118         }
119 
120         ap_set_content_type(r, "text/plain");
121 
122     }
123 
124     /* Do nothing if asked to filter nothing. */
125     if (APR_BRIGADE_EMPTY(bb)) {
126         return ap_pass_brigade(f->next, bb);
127     }
128 
129     while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
130         const char *data;
131         apr_size_t size;
132         apr_size_t tail;
133         apr_size_t len;
134         /* buffer big enough for 8000 encoded bytes (6000 raw bytes) and terminator */
135         char buffer[APR_BUCKET_BUFF_SIZE + 1];
136         char encoded[((sizeof(ctx->overflow)) / 3) * 4 + 1];
137 
138         e = APR_BRIGADE_FIRST(bb);
139 
140         /* EOS means we are done. */
141         if (APR_BUCKET_IS_EOS(e)) {
142 
143             /* write away the tail */
144             if (ctx->count) {
145                 len = apr_base64_encode_binary(encoded, ctx->overflow,
146                         ctx->count);
147                 apr_brigade_write(ctx->bb, NULL, NULL, encoded, len - 1);
148                 ctx->count = 0;
149             }
150 
151             /* pass the EOS across */
152             APR_BUCKET_REMOVE(e);
153             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
154 
155             /* pass what we have down the chain */
156             ap_remove_output_filter(f);
157             rv = ap_pass_brigade(f->next, ctx->bb);
158 
159             /* pass any stray buckets after the EOS down the stack */
160             if ((APR_SUCCESS == rv) && (!APR_BRIGADE_EMPTY(bb))) {
161                rv = ap_pass_brigade(f->next, bb);
162             }
163             continue;
164         }
165 
166         /* flush what we can, we can't flush the tail until EOS */
167         if (APR_BUCKET_IS_FLUSH(e)) {
168 
169             /* pass the flush bucket across */
170             APR_BUCKET_REMOVE(e);
171             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
172 
173             /* pass what we have down the chain */
174             rv = ap_pass_brigade(f->next, ctx->bb);
175             continue;
176         }
177 
178         /* metadata buckets are preserved as is */
179         if (APR_BUCKET_IS_METADATA(e)) {
180             /*
181              * Remove meta data bucket from old brigade and insert into the
182              * new.
183              */
184             APR_BUCKET_REMOVE(e);
185             APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
186             continue;
187         }
188 
189         /* make sure we don't read more than 6000 bytes at a time */
190         apr_brigade_partition(bb, (APR_BUCKET_BUFF_SIZE / 4 * 3), &ee);
191 
192         /* size will never be more than 6000 bytes */
193         if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size,
194                 APR_BLOCK_READ))) {
195 
196             /* fill up and write out our overflow buffer if partially used */
197             while (size && ctx->count && ctx->count < sizeof(ctx->overflow)) {
198                 ctx->overflow[ctx->count++] = *data++;
199                 size--;
200             }
201             if (ctx->count == sizeof(ctx->overflow)) {
202                 len = apr_base64_encode_binary(encoded, ctx->overflow,
203                         sizeof(ctx->overflow));
204                 apr_brigade_write(ctx->bb, NULL, NULL, encoded, len - 1);
205                 ctx->count = 0;
206             }
207 
208             /* write the main base64 chunk */
209             tail = size % sizeof(ctx->overflow);
210             size -= tail;
211             if (size) {
212                 len = apr_base64_encode_binary(buffer,
213                         (const unsigned char *) data, size);
214                 apr_brigade_write(ctx->bb, NULL, NULL, buffer, len - 1);
215             }
216 
217             /* save away any tail in the overflow buffer */
218             if (tail) {
219                 memcpy(ctx->overflow, data + size, tail);
220                 ctx->count += tail;
221             }
222 
223             apr_bucket_delete(e);
224 
225             /* pass what we have down the chain */
226             rv = ap_pass_brigade(f->next, ctx->bb);
227             if (rv) {
228                 /* should break out of the loop, since our write to the client
229                  * failed in some way. */
230                 continue;
231             }
232 
233         }
234 
235     }
236 
237     return rv;
238 
239 }
240 
241 static const command_rec data_cmds[] = { { NULL } };
242 
register_hooks(apr_pool_t * p)243 static void register_hooks(apr_pool_t *p)
244 {
245     ap_register_output_filter(DATA_FILTER, data_out_filter, NULL,
246             AP_FTYPE_RESOURCE);
247 }
248 AP_DECLARE_MODULE(data) = { STANDARD20_MODULE_STUFF,
249     NULL,  /* create per-directory config structure */
250     NULL, /* merge per-directory config structures */
251     NULL, /* create per-server config structure */
252     NULL, /* merge per-server config structures */
253     data_cmds, /* command apr_table_t */
254     register_hooks /* register hooks */
255 };
256