static int req_set_etag(lua_State* L) { request_rec *r = CHECK_REQUEST_OBJECT(1); ap_set_etag(r); return 0; }
static dav_error * dav_rawx_set_headers(request_rec *r, const dav_resource *resource) { if (!resource->exists) return NULL; DAV_DEBUG_REQ(r, 0, "%s(%s)", __FUNCTION__, resource_get_pathname(resource)); /* make sure the proper mtime is in the request record */ ap_update_mtime(r, resource->info->finfo.mtime); ap_set_last_modified(r); ap_set_etag(r); /* we accept byte-ranges */ apr_table_setn(r->headers_out, apr_pstrdup(r->pool, "Accept-Ranges"), apr_pstrdup(r->pool, "bytes")); /* set up the Content-Length header */ ap_set_content_length(r, resource->info->finfo.size); request_fill_headers(r, &(resource->info->chunk)); /* compute metadata_compress if compressed content */ if (resource->info->compression) { char *buf = apr_pstrcat(r->pool, "compression=on;compression_algorithm=", resource->info->compress_algo, ";compression_blocksize=", apr_psprintf(r->pool, "%d", resource->info->cp_chunk.block_size), ";", NULL); apr_table_setn(r->headers_out, apr_pstrdup(r->pool, "metadatacompress"), buf); } return NULL; }
/* Apache Handle Process and Output */ static int req_meets(lua_State*L ) { request_rec *r = CHECK_REQUEST_OBJECT(1); apr_time_t mtime = luaL_checkint(L, 2); int len = luaL_checkint(L, 3); int status; if (mtime) ap_update_mtime(r, mtime * APR_USEC_PER_SEC); ap_set_last_modified(r); ap_set_etag(r); ap_set_accept_ranges(r); apr_table_setn(r->headers_out, "Content-Length", apr_itoa(r->pool, len)); status = ap_meets_conditions(r); if (status == 0) { lua_pushnil(L); } else lua_pushinteger(L, status); return 1; }
static apr_status_t ap_xsendfile_output_filter(ap_filter_t *f, apr_bucket_brigade *in) { request_rec *r = f->r, *sr = NULL; xsendfile_conf_t *dconf = (xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module), *sconf = (xsendfile_conf_t *)ap_get_module_config(r->server->module_config, &xsendfile_module), *conf = xsendfile_config_merge(r->pool, sconf, dconf); core_dir_config *coreconf = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module); apr_status_t rv; apr_bucket *e; apr_file_t *fd = NULL; apr_finfo_t finfo; const char *file = NULL; char *translated = NULL; int errcode; #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: output_filter for %s", r->the_request); #endif /* should we proceed with this request? * sub-requests suck * furthermore default-handled requests suck, as they actually shouldn't be able to set headers */ if ( r->status != HTTP_OK || r->main || (r->handler && strcmp(r->handler, "default-handler") == 0) /* those table-keys are lower-case, right? */ ) { #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: not met [%d]", r->status); #endif ap_remove_output_filter(f); return ap_pass_brigade(f->next, in); } /* alright, look for x-sendfile */ file = apr_table_get(r->headers_out, AP_XSENDFILE_HEADER); apr_table_unset(r->headers_out, AP_XSENDFILE_HEADER); /* cgi/fastcgi will put the stuff into err_headers_out */ if (!file || !*file) { file = apr_table_get(r->err_headers_out, AP_XSENDFILE_HEADER); apr_table_unset(r->err_headers_out, AP_XSENDFILE_HEADER); } /* nothing there :p */ if (!file || !*file) { #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: nothing found"); #endif ap_remove_output_filter(f); return ap_pass_brigade(f->next, in); } /* drop *everything* might be pretty expensive to generate content first that goes straight to the bitbucket, but actually the scripts that might set this flag won't output too much anyway */ while (!APR_BRIGADE_EMPTY(in)) { e = APR_BRIGADE_FIRST(in); apr_bucket_delete(e); } r->eos_sent = 0; rv = ap_xsendfile_get_filepath(r, conf, file, &translated); if (rv != OK) { ap_log_rerror( APLOG_MARK, APLOG_ERR, rv, r, "xsendfile: unable to find file: %s", file ); ap_remove_output_filter(f); ap_die(HTTP_NOT_FOUND, r); return HTTP_NOT_FOUND; } #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: found %s", translated); #endif /* ry open the file */ if ((rv = apr_file_open( &fd, translated, APR_READ | APR_BINARY #if APR_HAS_SENDFILE | (coreconf->enable_sendfile == ENABLE_SENDFILE_ON ? APR_SENDFILE_ENABLED : 0) #endif , 0, r->pool )) != APR_SUCCESS) { ap_log_rerror( APLOG_MARK, APLOG_ERR, rv, r, "xsendfile: cannot open file: %s", translated ); ap_remove_output_filter(f); ap_die(HTTP_NOT_FOUND, r); return HTTP_NOT_FOUND; } #if APR_HAS_SENDFILE && defined(_DEBUG) if (coreconf->enable_sendfile != ENABLE_SENDFILE_ON) { ap_log_error( APLOG_MARK, APLOG_WARNING, 0, r->server, "xsendfile: sendfile configured, but not active %d", coreconf->enable_sendfile ); } #endif /* stat (for etag/cache/content-length stuff) */ if ((rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd)) != APR_SUCCESS) { ap_log_rerror( APLOG_MARK, APLOG_ERR, rv, r, "xsendfile: unable to stat file: %s", translated ); apr_file_close(fd); ap_remove_output_filter(f); ap_die(HTTP_FORBIDDEN, r); return HTTP_FORBIDDEN; } /* no inclusion of directories! we're serving files! */ if (finfo.filetype != APR_REG) { ap_log_rerror( APLOG_MARK, APLOG_ERR, APR_EBADPATH, r, "xsendfile: not a file %s", translated ); apr_file_close(fd); ap_remove_output_filter(f); ap_die(HTTP_NOT_FOUND, r); return HTTP_NOT_FOUND; } /* need to cheat here a bit as etag generator will use those ;) and we want local_copy and cache */ r->finfo.inode = finfo.inode; r->finfo.size = finfo.size; /* caching? why not :p */ r->no_cache = r->no_local_copy = 0; /* some script (f?cgi) place stuff in err_headers_out */ if ( conf->ignoreLM == XSENDFILE_ENABLED || ( !apr_table_get(r->headers_out, "last-modified") && !apr_table_get(r->headers_out, "last-modified") ) ) { apr_table_unset(r->err_headers_out, "last-modified"); ap_update_mtime(r, finfo.mtime); ap_set_last_modified(r); } if ( conf->ignoreETag == XSENDFILE_ENABLED || ( !apr_table_get(r->headers_out, "etag") && !apr_table_get(r->err_headers_out, "etag") ) ) { apr_table_unset(r->err_headers_out, "etag"); ap_set_etag(r); } apr_table_unset(r->err_headers_out, "content-length"); ap_set_content_length(r, finfo.size); /* as we dropped all the content this field is not valid anymore! */ apr_table_unset(r->headers_out, "Content-Encoding"); apr_table_unset(r->err_headers_out, "Content-Encoding"); /* cache or something? */ if ((errcode = ap_meets_conditions(r)) != OK) { #ifdef _DEBUG ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: met condition %d for %s", errcode, file ); #endif apr_file_close(fd); r->status = errcode; } else { /* For platforms where the size of the file may be larger than * that which can be stored in a single bucket (where the * length field is an apr_size_t), split it into several * buckets: */ if (sizeof(apr_off_t) > sizeof(apr_size_t) && finfo.size > AP_MAX_SENDFILE) { apr_off_t fsize = finfo.size; e = apr_bucket_file_create(fd, 0, AP_MAX_SENDFILE, r->pool, in->bucket_alloc); while (fsize > AP_MAX_SENDFILE) { apr_bucket *ce; apr_bucket_copy(e, &ce); APR_BRIGADE_INSERT_TAIL(in, ce); e->start += AP_MAX_SENDFILE; fsize -= AP_MAX_SENDFILE; } e->length = (apr_size_t)fsize; /* Resize just the last bucket */ } else { e = apr_bucket_file_create(fd, 0, (apr_size_t)finfo.size, r->pool, in->bucket_alloc); } #if APR_HAS_MMAP if (coreconf->enable_mmap == ENABLE_MMAP_ON) { apr_bucket_file_enable_mmap(e, 0); } #if defined(_DEBUG) else { ap_log_error( APLOG_MARK, APLOG_WARNING, 0, r->server, "xsendfile: mmap configured, but not active %d", coreconf->enable_mmap ); } #endif /* _DEBUG */ #endif /* APR_HAS_MMAP */ APR_BRIGADE_INSERT_TAIL(in, e); } e = apr_bucket_eos_create(in->bucket_alloc); APR_BRIGADE_INSERT_TAIL(in, e); /* remove ourselves from the filter chain */ ap_remove_output_filter(f); #ifdef _DEBUG ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: sending %d bytes", (int)finfo.size); #endif /* send the data up the stack */ return ap_pass_brigade(f->next, in); }
static int file_cache_handler(request_rec *r) { a_file *match; int errstatus; int rc = OK; /* Bail out if r->handler isn't the default value, and doesn't look like a Content-Type * XXX: Even though we made the user explicitly list each path to cache? */ if (ap_strcmp_match(r->handler, "*/*") && !AP_IS_DEFAULT_HANDLER_NAME(r->handler)) { return DECLINED; } /* we don't handle anything but GET */ if (r->method_number != M_GET) return DECLINED; /* did xlat phase find the file? */ match = ap_get_module_config(r->request_config, &file_cache_module); if (match == NULL) { return DECLINED; } /* note that we would handle GET on this resource */ r->allowed |= (AP_METHOD_BIT << M_GET); /* This handler has no use for a request body (yet), but we still * need to read and discard it if the client sent one. */ if ((errstatus = ap_discard_request_body(r)) != OK) return errstatus; ap_update_mtime(r, match->finfo.mtime); /* ap_set_last_modified() always converts the file mtime to a string * which is slow. Accelerate the common case. * ap_set_last_modified(r); */ { apr_time_t mod_time; char *datestr; mod_time = ap_rationalize_mtime(r, r->mtime); if (mod_time == match->finfo.mtime) datestr = match->mtimestr; else { datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN); apr_rfc822_date(datestr, mod_time); } apr_table_setn(r->headers_out, "Last-Modified", datestr); } /* ap_set_content_length() always converts the same number and never * returns an error. Accelerate it. */ r->clength = match->finfo.size; apr_table_setn(r->headers_out, "Content-Length", match->sizestr); ap_set_etag(r); if ((errstatus = ap_meets_conditions(r)) != OK) { return errstatus; } /* Call appropriate handler */ if (!r->header_only) { if (match->is_mmapped == TRUE) rc = mmap_handler(r, match); else rc = sendfile_handler(r, match); } return rc; }
/* The content handler */ static int rcache_handler(request_rec *r) { rcache_info *info; info = ap_get_module_config(r->server->module_config, &rcache_module); info->data = ""; info->r = r; info->length = 0; info->type = ""; apr_status_t rv; const char *retrieve_url; if (strcmp(r->handler, "rcache")) { rv = DECLINED; goto finish; } // redis context and connection #ifdef CONN_PERSISTENT redisContext *c = info->c; redisReply *reply = info->reply; if (!c) { struct timeval timeout = { 1, 500000 }; // 1.5 seconds c = redisConnectWithTimeout(info->hostname, info->port, timeout); info->c = c; // ping pong test if ( !(c == NULL || c->err) ) { reply = redisCommand(c,"PING"); #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "PING: %s", reply->str); #endif } } #else redisContext *c; redisReply *reply; struct timeval timeout = { 1, 500000 }; // 1.5 seconds c = redisConnectWithTimeout(info->hostname, info->port, timeout); #endif // when redis connection error if (c == NULL || c->err) { if (c) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "Connection error: %s", c->errstr); } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "Connection error: can't allocate redis context" ); } rv = HTTP_INTERNAL_SERVER_ERROR; goto finish; } #ifdef FLAG_WAIT int wait = 0; #endif if ( (retrieve_url = apr_table_get(r->subprocess_env, info->env_retrieve_url)) ) { #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "%s", r->path_info); ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "%s", retrieve_url); #endif #ifdef FLAG_WAIT // WAIT flag check. reply = redisCommand(c, "GET %s%s", info->prefix_wait, r->path_info); if( reply->type != REDIS_REPLY_NIL ) { // cache hot-replace check. if ( reply->type == REDIS_REPLY_STRING && 0 == strcmp(reply->str,"HOT") ){ redisCommand(c, "DEL %s%s", info->prefix_wait, r->path_info); #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "hot cache replace: %s", r->path_info ); #endif goto gencache; } // wait cache create. wait = 1; int loop = 10; while(loop){ reply = redisCommand(c, "GET %s%s", info->prefix_wait, r->path_info); #ifdef DEBUG ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "wait loop: remain %d sec", loop); #endif if( reply->type == REDIS_REPLY_NIL ) { goto read; } sleep(1); loop--; } // when timeup. delete flag. redisCommand(c, "DEL %s%s", info->prefix_wait, r->path_info); goto gencache; } #endif read: // read from redis. reply = redisCommand(c, "HMGET %s%s CONTENT TYPE LENGTH MTIME", info->prefix, r->path_info); if( reply->type == REDIS_REPLY_ARRAY ) { if ( reply->element[0]->len == 0 ) goto gencache; const char* tmp = apr_pstrdup(r->pool, reply->element[1]->str); ap_set_content_type(r, tmp); ap_set_content_length(r, atoi(reply->element[2]->str) ); apr_time_t time = atol(reply->element[3]->str); ap_update_mtime(r, time); ap_set_last_modified(r); ap_set_etag(r); apr_status_t rc = ap_meets_conditions(r); if (rc != OK) { rv = rc; goto finish; } if (!r->header_only) ap_rputs(reply->element[0]->str, r); rv = OK; goto finish; } }else{ rv = DECLINED; goto finish; } gencache: #ifdef FLAG_WAIT if (wait == 0) redisCommand(c, "SET %s%s 1", info->prefix_wait, r->path_info); #endif rv = rcache_curl(retrieve_url, info); // set content to redis if (rv == HTTP_OK) { if (strcmp(info->type,"")) info->type = "text/html"; if (info->length == 0) info->length = strlen(info->data); redisCommand(c,"HMSET %s%s CONTENT %s TYPE %s LENGTH %d MTIME %ld", info->prefix, r->path_info, info->data, info->type, info->length, info->mtime); ap_set_content_type(r, info->type); ap_set_content_length(r, info->length); ap_update_mtime(r, info->mtime); ap_set_last_modified(r); ap_set_etag(r); if (!r->header_only) ap_rputs(info->data, r); } #ifdef FLAG_WAIT redisCommand(c, "DEL %s%s", info->prefix_wait, r->path_info); #endif finish: #ifndef CONN_PERSISTENT freeReplyObject(reply); redisFree(c); #endif return rv; }
/** * The output filter routine. This one gets called whenever a response is * generated that passes this filter. Returns APR_SUCCESS if everything works * out. * * @param f The filter definition. * @param bb The bucket brigade containing the data. */ static apr_status_t replace_output_filter(ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; conn_rec *c = r->connection; replace_ctx_t *ctx = f->ctx; apr_bucket *b; apr_size_t len; const char *data; const char *header; apr_status_t rv; int re_vector[RE_VECTOR_SIZE]; // 3 elements per matched pattern replace_pattern_t *next; header_replace_pattern_t *next_header; int modified = 0; // flag to determine if a replacement has // occured. if (!ctx) { /* Initialize context */ ctx = apr_pcalloc(f->r->pool, sizeof(replace_ctx_t)); f->ctx = ctx; ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); } /* parse config settings */ /* look for the user-defined filter */ ctx->filter = find_filter_def(f->r->server, f->frec->name); if (!ctx->filter) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "couldn't find definition of filter '%s'", f->frec->name); return APR_EINVAL; } ctx->p = f->r->pool; if (ctx->filter->intype && ctx->filter->intype != INTYPE_ALL) { if (!f->r->content_type) { ctx->noop = 1; } else { const char *ctypes = f->r->content_type; const char *ctype = ap_getword(f->r->pool, &ctypes, ';'); if (strcasecmp(ctx->filter->intype, ctype)) { /* wrong IMT for us; don't mess with the output */ ctx->noop = 1; } } } /* exit immediately if there are indications that the filter shouldn't be * executed. */ if (ctx->noop == 1) { ap_pass_brigade(f->next, bb); return APR_SUCCESS; } /** * Loop through the configured header patterns. */ for (next_header = ctx->filter->header_pattern; next_header != NULL; next_header = next_header->next) { // create a separate table with the requested HTTP header entries and // unset those headers in the original request. apr_table_t *header_table; header_table = apr_table_make(r->pool, 2); // create a data structure for the callback function header_replace_cb_t *hrcb; hrcb = apr_palloc(r->pool, sizeof(header_replace_cb_t)); hrcb->header_table = header_table; hrcb->pattern = next_header->pattern; hrcb->extra = next_header->extra; hrcb->replacement = next_header->replacement; hrcb->r = r; // pass any header that is defined to be processed to the callback // function and unset those headers in the original outgoing record. apr_table_do(replace_header_cb, hrcb, r->headers_out, next_header->header, NULL); // only touch the header if the changed header table is not empty. if (!apr_is_empty_table(header_table)) { apr_table_unset(r->headers_out, next_header->header); // overlay the original header table with the new one to reintegrate // the changed headers. r->headers_out = apr_table_overlay(r->pool, r->headers_out, header_table); } } /* Not nice but neccessary: Unset the ETag , because we cannot adjust the * value correctly, because we do not know how. */ apr_table_unset(f->r->headers_out, "ETag"); int eos = 0; // flag to check if an EOS bucket is in the brigade. apr_bucket *eos_bucket; // Backup for the EOS bucket. /* Interate through the available data. Stop if there is an EOS */ for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { if (APR_BUCKET_IS_EOS(b)) { eos = 1; ap_save_brigade(f, &ctx->bb, &bb, ctx->p); APR_BUCKET_REMOVE(b); eos_bucket = b; break; } } /* If the iteration over the brigade hasn't found an EOS bucket, just save * the brigade and return. */ if (eos != 1) { ap_save_brigade(f, &ctx->bb, &bb, ctx->p); return APR_SUCCESS; } if ((rv = apr_brigade_pflatten(ctx->bb, (char **)&data, &len, ctx->p)) != APR_SUCCESS) { /* Return if the flattening didn't work. */ return rv; } else { /* Remove the original data from the bucket brigade. Otherwise it would * be passed twice (original data and the processed, flattened copy) to * the next filter. */ apr_brigade_cleanup(ctx->bb); } /* Good cast, we just tested len isn't negative or zero */ if (len > 0) { /* start checking for the regex's. */ for (next = ctx->filter->pattern; next != NULL; next = next->next) { int rc = 0; int offset = 0; /* loop through the configured patterns */ do { rc = pcre_exec(next->pattern, next->extra, data, len, offset, 0, re_vector, RE_VECTOR_SIZE); if (rc < 0 && rc != PCRE_ERROR_NOMATCH) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, "Matching Error %d", rc); return rc; } /* This shouldn´t happen */ if (rc == 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, "PCRE output vector too small (%d)", RE_VECTOR_SIZE/3-1); } /* If the result count is greater than 0 then there are * matches in the data string. Thus we try to replace those * strings with the user provided string. */ if (rc > 0) { char *prefix; // the string before the matching part. char *postfix; // the string after the matching part. char *newdata; // the concatenated string of prefix, // the replaced string and postfix. char *replacement; // the string with the data to replace // (after the subpattern processing has // been done). char *to_replace[10]; // the string array containing the // strings that are to be replaced. int match_diff; // the difference between the matching // string and its replacement. int x; // a simple counter. char *pos; // the starting position within the // replacement string, where there is a // subpattern to replace. /* start with building the replacement string */ replacement = apr_pstrcat(ctx->p, next->replacement, NULL); /* look for the subpatterns \0 to \9 */ for (x = 0; x < rc && x < 10; x++) { /* extract the x'ths subpattern */ to_replace[x] = substr(data, re_vector[x*2], re_vector[x*2+1] - re_vector[x*2], r); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Found match: %s", to_replace[x]); /* the token ( \0 to \9) we are looking for */ char *token = apr_pstrcat(ctx->p, "\\", apr_itoa(ctx->p, x), NULL); /* allocate memory for the replacement operation */ char *tmp; if (!to_replace[x] || strlen(to_replace[x]) < 2) { tmp = malloc(strlen(replacement) + 1); } else { tmp = malloc(strlen(replacement) - 1 + strlen(to_replace[x])); } /* copy the replacement string to the new * location. */ memcpy(tmp, replacement, strlen(replacement) + 1); replacement = tmp; /* try to replace each occurence of the token with * its matched subpattern. */ pos = ap_strstr(replacement, token); while (pos) { if (!to_replace[x]) { break; } substr_replace(pos, to_replace[x], strlen(pos), strlen(to_replace[x])); if (strlen(to_replace[x]) < 2) { tmp = malloc(strlen(replacement) + 1); } else { tmp = malloc(strlen(replacement) - 1 + strlen(to_replace[x])); } memcpy(tmp, replacement, strlen(replacement) + 1); /* clean up. */ free(replacement); replacement = tmp; pos = ap_strstr(replacement, token); } } match_diff = strlen(replacement) - (re_vector[1] - re_vector[0]); /* Allocate memory for a buffer to copy the first part * of the data string up to (but not including) the * the matching pattern. */ prefix = apr_pcalloc(ctx->p, re_vector[0] + 1); if (prefix == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unable to allocate memory for prefix", NULL); return -1; } /* Copy the string from the offset (beginning of * pattern matching) to the first occurence of the * pattern and add a trailing \0. */ memcpy(prefix, data, (size_t)re_vector[0]); /* Copy the string from the end of the pattern to the * end of the data string itself. */ postfix = apr_pcalloc(ctx->p, len); if (postfix == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unable to allocate memory for postfix", NULL); return -1; } memcpy(postfix, (data + re_vector[1]), len - re_vector[1]); /* Create the new data string, replace the old one * and clean up. */ newdata = apr_pstrcat(ctx->p, prefix, replacement, postfix, NULL); /* update the point of the data and free the allocated * memory for the replacement string. */ data = newdata; free(replacement); /* Calculate the new offset in the data string, where * the new matching round is to begin. */ offset = re_vector[1] + match_diff; len += match_diff; modified = 1; } } while (rc > 0); } /* Adjust the real length of the processed data. */ if (apr_table_get(f->r->headers_out, "Content-Length") != NULL) { apr_table_set(f->r->headers_out, "Content-Length", apr_itoa(ctx->p, len)); } /* If an Entity Tag is set, change the mtime and generate a new ETag.*/ if (apr_table_get(f->r->headers_out, "ETag") != NULL) { r->mtime = time(NULL); ap_set_etag(r); } } /* Create a new bucket with the processed data, insert that one into our * brigade, then insert the saved EOS bucket at the end of the brigade * and pass the brigade to the next filter. */ APR_BRIGADE_INSERT_TAIL(ctx->bb, apr_bucket_transient_create(data, len, apr_bucket_alloc_create(ctx->p))); APR_BRIGADE_INSERT_TAIL(ctx->bb, eos_bucket); ap_pass_brigade(f->next, ctx->bb); return APR_SUCCESS; }