static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info) { disk_cache_conf *conf = ap_get_module_config(r->server->module_config, &disk_cache_module); apr_status_t rv; apr_size_t amt; disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj; disk_cache_info_t disk_info; struct iovec iov[2]; /* This is flaky... we need to manage the cache_info differently */ h->cache_obj->info = *info; if (r->headers_out) { const char *tmp; tmp = apr_table_get(r->headers_out, "Vary"); if (tmp) { apr_array_header_t* varray; apr_uint32_t format = VARY_FORMAT_VERSION; /* If we were initially opened as a vary format, rollback * that internal state for the moment so we can recreate the * vary format hints in the appropriate directory. */ if (dobj->prefix) { dobj->hdrsfile = dobj->prefix; dobj->prefix = NULL; } mkdir_structure(conf, dobj->hdrsfile, r->pool); rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile, APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL, r->pool); if (rv != APR_SUCCESS) { return rv; } amt = sizeof(format); apr_file_write(dobj->tfd, &format, &amt); amt = sizeof(info->expire); apr_file_write(dobj->tfd, &info->expire, &amt); varray = apr_array_make(r->pool, 6, sizeof(char*)); tokens_to_array(r->pool, tmp, varray); store_array(dobj->tfd, varray); apr_file_close(dobj->tfd); dobj->tfd = NULL; rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server, "disk_cache: rename tempfile to varyfile failed: %s -> %s", dobj->tempfile, dobj->hdrsfile); apr_file_remove(dobj->tempfile, r->pool); return rv; } dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL); tmp = regen_key(r->pool, r->headers_in, varray, dobj->name); dobj->prefix = dobj->hdrsfile; dobj->hashfile = NULL; dobj->datafile = data_file(r->pool, conf, dobj, tmp); dobj->hdrsfile = header_file(r->pool, conf, dobj, tmp); } } rv = apr_file_mktemp(&dobj->hfd, dobj->tempfile, APR_CREATE | APR_WRITE | APR_BINARY | APR_BUFFERED | APR_EXCL, r->pool); if (rv != APR_SUCCESS) { return rv; } disk_info.format = DISK_FORMAT_VERSION; disk_info.date = info->date; disk_info.expire = info->expire; disk_info.entity_version = dobj->disk_info.entity_version++; disk_info.request_time = info->request_time; disk_info.response_time = info->response_time; disk_info.status = info->status; disk_info.name_len = strlen(dobj->name); iov[0].iov_base = (void*)&disk_info; iov[0].iov_len = sizeof(disk_cache_info_t); iov[1].iov_base = (void*)dobj->name; iov[1].iov_len = disk_info.name_len; rv = apr_file_writev(dobj->hfd, (const struct iovec *) &iov, 2, &amt); if (rv != APR_SUCCESS) { return rv; } if (r->headers_out) { apr_table_t *headers_out; headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out, r->server); if (!apr_table_get(headers_out, "Content-Type") && r->content_type) { apr_table_setn(headers_out, "Content-Type", ap_make_content_type(r, r->content_type)); } headers_out = apr_table_overlay(r->pool, headers_out, r->err_headers_out); rv = store_table(dobj->hfd, headers_out); if (rv != APR_SUCCESS) { return rv; } } /* Parse the vary header and dump those fields from the headers_in. */ /* FIXME: Make call to the same thing cache_select calls to crack Vary. */ if (r->headers_in) { apr_table_t *headers_in; headers_in = ap_cache_cacheable_hdrs_out(r->pool, r->headers_in, r->server); rv = store_table(dobj->hfd, headers_in); if (rv != APR_SUCCESS) { return rv; } } apr_file_close(dobj->hfd); /* flush and close */ /* Remove old file with the same name. If remove fails, then * perhaps we need to create the directory tree where we are * about to write the new headers file. */ rv = apr_file_remove(dobj->hdrsfile, r->pool); if (rv != APR_SUCCESS) { mkdir_structure(conf, dobj->hdrsfile, r->pool); } rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, r->pool); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server, "disk_cache: rename tempfile to hdrsfile failed: %s -> %s", dobj->tempfile, dobj->hdrsfile); apr_file_remove(dobj->tempfile, r->pool); return rv; } dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "disk_cache: Stored headers for URL %s", dobj->name); return APR_SUCCESS; }
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; conn_rec *c = r->connection; apr_bucket *e; apr_bucket_brigade *bsend; apr_bucket_brigade *tmpbb; apr_off_t range_start; apr_off_t range_end; apr_off_t clength = 0; apr_status_t rv; int found = 0; int num_ranges; char *boundary = NULL; char *bound_head = NULL; apr_array_header_t *indexes; indexes_t *idx; int i; int original_status; int max_ranges = get_max_ranges(r); /* * Iterate through the brigade until reaching EOS or a bucket with * unknown length. */ for (e = APR_BRIGADE_FIRST(bb); (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e) && e->length != (apr_size_t)-1); e = APR_BUCKET_NEXT(e)) { clength += e->length; } /* * Don't attempt to do byte range work if this brigade doesn't * contain an EOS, or if any of the buckets has an unknown length; * this avoids the cases where it is expensive to perform * byteranging (i.e. may require arbitrary amounts of memory). */ if (!APR_BUCKET_IS_EOS(e) || clength <= 0) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } original_status = r->status; num_ranges = ap_set_byterange(r, clength, &indexes); /* We have nothing to do, get out of the way. */ if (num_ranges == 0 || (max_ranges >= 0 && num_ranges > max_ranges)) { r->status = original_status; ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } /* this brigade holds what we will be sending */ bsend = apr_brigade_create(r->pool, c->bucket_alloc); if (num_ranges < 0) return send_416(f, bsend); if (num_ranges > 1) { /* Is ap_make_content_type required here? */ const char *orig_ct = ap_make_content_type(r, r->content_type); boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx", (apr_uint64_t)r->request_time, c->id); ap_set_content_type(r, apr_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/", "byteranges; boundary=", boundary, NULL)); if (strcasecmp(orig_ct, NO_CONTENT_TYPE)) { bound_head = apr_pstrcat(r->pool, CRLF "--", boundary, CRLF "Content-type: ", orig_ct, CRLF "Content-range: bytes ", NULL); } else { /* if we have no type for the content, do our best */ bound_head = apr_pstrcat(r->pool, CRLF "--", boundary, CRLF "Content-range: bytes ", NULL); } ap_xlate_proto_to_ascii(bound_head, strlen(bound_head)); } tmpbb = apr_brigade_create(r->pool, c->bucket_alloc); idx = (indexes_t *)indexes->elts; for (i = 0; i < indexes->nelts; i++, idx++) { range_start = idx->start; range_end = idx->end; rv = copy_brigade_range(bb, tmpbb, range_start, range_end); if (rv != APR_SUCCESS ) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "copy_brigade_range() failed [%" APR_OFF_T_FMT "-%" APR_OFF_T_FMT ",%" APR_OFF_T_FMT "]", range_start, range_end, clength); continue; } found = 1; /* * For single range requests, we must produce Content-Range header. * Otherwise, we need to produce the multipart boundaries. */ if (num_ranges == 1) { apr_table_setn(r->headers_out, "Content-Range", apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, range_start, range_end, clength)); } else { char *ts; e = apr_bucket_pool_create(bound_head, strlen(bound_head), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF, range_start, range_end, clength); ap_xlate_proto_to_ascii(ts, strlen(ts)); e = apr_bucket_pool_create(ts, strlen(ts), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); } APR_BRIGADE_CONCAT(bsend, tmpbb); if (i && !(i & 0x1F)) { /* * Every now and then, pass what we have down the filter chain. * In this case, the content-length filter cannot calculate and * set the content length and we must remove any Content-Length * header already present. */ apr_table_unset(r->headers_out, "Content-Length"); if ((rv = ap_pass_brigade(f->next, bsend)) != APR_SUCCESS) return rv; apr_brigade_cleanup(bsend); } } if (found == 0) { /* bsend is assumed to be empty if we get here. */ return send_416(f, bsend); } if (num_ranges > 1) { char *end; /* add the final boundary */ end = apr_pstrcat(r->pool, CRLF "--", boundary, "--" CRLF, NULL); ap_xlate_proto_to_ascii(end, strlen(end)); e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); } e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); /* we're done with the original content - all of our data is in bsend. */ apr_brigade_cleanup(bb); apr_brigade_destroy(tmpbb); /* send our multipart output */ return ap_pass_brigade(f->next, bsend); }
static apr_status_t store_headers(cache_handle_t *h, request_rec *r, cache_info *info) { cache_object_t *obj = h->cache_obj; mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj; apr_table_t *headers_out; /* * The cache needs to keep track of the following information: * - Date, LastMod, Version, ReqTime, RespTime, ContentLength * - The original request headers (for Vary) * - The original response headers (for returning with a cached response) * - The body of the message */ if (mobj->lock) { apr_thread_mutex_lock(mobj->lock); } mobj->req_hdrs = deep_table_copy(mobj->pool, r->headers_in); if (mobj->lock) { apr_thread_mutex_unlock(mobj->lock); } /* Precompute how much storage we need to hold the headers */ headers_out = apr_table_overlay(r->pool, r->headers_out, r->err_headers_out); headers_out = ap_cache_cacheable_hdrs_out(r->pool, headers_out, r->server); /* If not set in headers_out, set Content-Type */ if (!apr_table_get(headers_out, "Content-Type") && r->content_type) { apr_table_setn(headers_out, "Content-Type", ap_make_content_type(r, r->content_type)); } if (!apr_table_get(headers_out, "Content-Encoding") && r->content_encoding) { apr_table_setn(headers_out, "Content-Encoding", r->content_encoding); } if (mobj->lock) { apr_thread_mutex_lock(mobj->lock); } mobj->header_out = deep_table_copy(mobj->pool, headers_out); if (mobj->lock) { apr_thread_mutex_unlock(mobj->lock); } /* Init the info struct */ obj->info.status = info->status; if (info->date) { obj->info.date = info->date; } if (info->response_time) { obj->info.response_time = info->response_time; } if (info->request_time) { obj->info.request_time = info->request_time; } if (info->expire) { obj->info.expire = info->expire; } return APR_SUCCESS; }
/* * select a specific URL entity in the cache * * It is possible to store more than one entity per URL. Content * negotiation is used to select an entity. Once an entity is * selected, details of it are stored in the per request * config to save time when serving the request later. * * This function returns OK if successful, DECLINED if no * cached entity fits the bill. */ int cache_select_url(request_rec *r, char *url) { cache_provider_list *list; apr_status_t rv; cache_handle_t *h; char *key; cache_request_rec *cache = (cache_request_rec *) ap_get_module_config(r->request_config, &cache_module); rv = cache_generate_key(r, r->pool, &key); if (rv != APR_SUCCESS) { return rv; } /* go through the cache types till we get a match */ h = apr_palloc(r->pool, sizeof(cache_handle_t)); list = cache->providers; while (list) { switch ((rv = list->provider->open_entity(h, r, key))) { case OK: { char *vary = NULL; const char *varyhdr = NULL; int fresh; if (list->provider->recall_headers(h, r) != APR_SUCCESS) { /* TODO: Handle this error */ return DECLINED; } /* * Check Content-Negotiation - Vary * * At this point we need to make sure that the object we found in * the cache is the same object that would be delivered to the * client, when the effects of content negotiation are taken into * effect. * * In plain english, we want to make sure that a language-negotiated * document in one language is not given to a client asking for a * language negotiated document in a different language by mistake. * * This code makes the assumption that the storage manager will * cache the req_hdrs if the response contains a Vary * header. * * RFC2616 13.6 and 14.44 describe the Vary mechanism. */ if ((varyhdr = apr_table_get(h->resp_err_hdrs, "Vary")) == NULL) { varyhdr = apr_table_get(h->resp_hdrs, "Vary"); } vary = apr_pstrdup(r->pool, varyhdr); while (vary && *vary) { char *name = vary; const char *h1, *h2; /* isolate header name */ while (*vary && !apr_isspace(*vary) && (*vary != ',')) ++vary; while (*vary && (apr_isspace(*vary) || (*vary == ','))) { *vary = '\0'; ++vary; } /* * is this header in the request and the header in the cached * request identical? If not, we give up and do a straight get */ h1 = apr_table_get(r->headers_in, name); h2 = apr_table_get(h->req_hdrs, name); if (h1 == h2) { /* both headers NULL, so a match - do nothing */ } else if (h1 && h2 && !strcmp(h1, h2)) { /* both headers exist and are equal - do nothing */ } else { /* headers do not match, so Vary failed */ ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, "cache_select_url(): Vary header mismatch."); return DECLINED; } } cache->provider = list->provider; cache->provider_name = list->provider_name; /* Is our cached response fresh enough? */ fresh = ap_cache_check_freshness(h, r); if (!fresh) { cache_info *info = &(h->cache_obj->info); /* Make response into a conditional */ /* FIXME: What if the request is already conditional? */ if (info && info->etag) { /* if we have a cached etag */ cache->stale_headers = apr_table_copy(r->pool, r->headers_in); apr_table_set(r->headers_in, "If-None-Match", info->etag); cache->stale_handle = h; } else if (info && info->lastmods) { /* if we have a cached Last-Modified header */ cache->stale_headers = apr_table_copy(r->pool, r->headers_in); apr_table_set(r->headers_in, "If-Modified-Since", info->lastmods); cache->stale_handle = h; } return DECLINED; } /* Okay, this response looks okay. Merge in our stuff and go. */ apr_table_setn(r->headers_out, "Content-Type", ap_make_content_type(r, h->content_type)); r->filename = apr_pstrdup(r->pool, h->cache_obj->info.filename); accept_headers(h, r); cache->handle = h; return OK; } case DECLINED: { /* try again with next cache type */ list = list->next; continue; } default: { /* oo-er! an error */ return rv; } } } return DECLINED; }
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, apr_bucket_brigade *bb) { #define MIN_LENGTH(len1, len2) ((len1 > len2) ? len2 : len1) request_rec *r = f->r; conn_rec *c = r->connection; byterange_ctx *ctx; apr_bucket *e; apr_bucket_brigade *bsend; apr_off_t range_start; apr_off_t range_end; char *current; apr_off_t clength = 0; apr_status_t rv; int found = 0; int num_ranges; /* Iterate through the brigade until reaching EOS or a bucket with * unknown length. */ for (e = APR_BRIGADE_FIRST(bb); (e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e) && e->length != (apr_size_t)-1); e = APR_BUCKET_NEXT(e)) { clength += e->length; } /* Don't attempt to do byte range work if this brigade doesn't * contain an EOS, or if any of the buckets has an unknown length; * this avoids the cases where it is expensive to perform * byteranging (i.e. may require arbitrary amounts of memory). */ if (!APR_BUCKET_IS_EOS(e) || clength <= 0) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } num_ranges = ap_set_byterange(r); /* We have nothing to do, get out of the way. */ if (num_ranges == 0) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } ctx = apr_pcalloc(r->pool, sizeof(*ctx)); ctx->num_ranges = num_ranges; /* create a brigade in case we never call ap_save_brigade() */ ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc); if (ctx->num_ranges > 1) { /* Is ap_make_content_type required here? */ const char *orig_ct = ap_make_content_type(r, r->content_type); ctx->boundary = apr_psprintf(r->pool, "%" APR_UINT64_T_HEX_FMT "%lx", (apr_uint64_t)r->request_time, (long) getpid()); ap_set_content_type(r, apr_pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/", "byteranges; boundary=", ctx->boundary, NULL)); if (strcasecmp(orig_ct, NO_CONTENT_TYPE)) { ctx->bound_head = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, CRLF "Content-type: ", orig_ct, CRLF "Content-range: bytes ", NULL); } else { /* if we have no type for the content, do our best */ ctx->bound_head = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, CRLF "Content-range: bytes ", NULL); } ap_xlate_proto_to_ascii(ctx->bound_head, strlen(ctx->bound_head)); } /* this brigade holds what we will be sending */ bsend = apr_brigade_create(r->pool, c->bucket_alloc); while ((current = ap_getword(r->pool, &r->range, ',')) && (rv = parse_byterange(current, clength, &range_start, &range_end))) { apr_bucket *e2; apr_bucket *ec; if (rv == -1) { continue; } /* These calls to apr_brigage_partition should only fail in * pathological cases, e.g. a file being truncated whilst * being served. */ if ((rv = apr_brigade_partition(bb, range_start, &ec)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, PARTITION_ERR_FMT, range_start, clength); continue; } if ((rv = apr_brigade_partition(bb, range_end+1, &e2)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, PARTITION_ERR_FMT, range_end+1, clength); continue; } found = 1; /* For single range requests, we must produce Content-Range header. * Otherwise, we need to produce the multipart boundaries. */ if (ctx->num_ranges == 1) { apr_table_setn(r->headers_out, "Content-Range", apr_psprintf(r->pool, "bytes " BYTERANGE_FMT, range_start, range_end, clength)); } else { char *ts; e = apr_bucket_pool_create(ctx->bound_head, strlen(ctx->bound_head), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); ts = apr_psprintf(r->pool, BYTERANGE_FMT CRLF CRLF, range_start, range_end, clength); ap_xlate_proto_to_ascii(ts, strlen(ts)); e = apr_bucket_pool_create(ts, strlen(ts), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); } do { apr_bucket *foo; const char *str; apr_size_t len; if (apr_bucket_copy(ec, &foo) != APR_SUCCESS) { /* As above; this should not fail since the bucket has * a known length, but just to be sure, this takes * care of uncopyable buckets that do somehow manage * to slip through. */ /* XXX: check for failure? */ apr_bucket_read(ec, &str, &len, APR_BLOCK_READ); apr_bucket_copy(ec, &foo); } APR_BRIGADE_INSERT_TAIL(bsend, foo); ec = APR_BUCKET_NEXT(ec); } while (ec != e2); } if (found == 0) { ap_remove_output_filter(f); r->status = HTTP_OK; /* bsend is assumed to be empty if we get here. */ e = ap_bucket_error_create(HTTP_RANGE_NOT_SATISFIABLE, NULL, r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); return ap_pass_brigade(f->next, bsend); } if (ctx->num_ranges > 1) { char *end; /* add the final boundary */ end = apr_pstrcat(r->pool, CRLF "--", ctx->boundary, "--" CRLF, NULL); ap_xlate_proto_to_ascii(end, strlen(end)); e = apr_bucket_pool_create(end, strlen(end), r->pool, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); } e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bsend, e); /* we're done with the original content - all of our data is in bsend. */ apr_brigade_cleanup(bb); /* send our multipart output */ return ap_pass_brigade(f->next, bsend); }