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
38module AP_MODULE_DECLARE_DATA data_module;
39
40typedef 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 */
65static 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            clen = apr_atoi64(content_length);
111            if (clen >= 0 && 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
241static const command_rec data_cmds[] = { { NULL } };
242
243static void register_hooks(apr_pool_t *p)
244{
245    ap_register_output_filter(DATA_FILTER, data_out_filter, NULL,
246            AP_FTYPE_RESOURCE);
247}
248AP_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