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