static void string_strtoff(abts_case *tc, void *data) { apr_off_t off; ABTS_ASSERT(tc, "strtoff fails on out-of-range integer", apr_strtoff(&off, "999999999999999999999999999999", NULL, 10) != APR_SUCCESS); ABTS_ASSERT(tc, "strtoff failed for 1234", apr_strtoff(&off, "1234", NULL, 10) == APR_SUCCESS); ABTS_ASSERT(tc, "strtoff failed to parse 1234", off == 1234); }
static int parse_byterange(char *range, apr_off_t clength, apr_off_t *start, apr_off_t *end) { char *dash = strchr(range, '-'); char *errp; apr_off_t number; if (!dash) { return 0; } if ((dash == range)) { /* In the form "-5" */ if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) { return 0; } *start = clength - number; *end = clength - 1; } else { *dash++ = '\0'; if (apr_strtoff(&number, range, &errp, 10) || *errp) { return 0; } *start = number; if (*dash) { if (apr_strtoff(&number, dash, &errp, 10) || *errp) { return 0; } *end = number; } else { /* "5-" */ *end = clength - 1; } } if (*start < 0) { *start = 0; } if (*end >= clength) { *end = clength - 1; } if (*start > *end) { return -1; } return (*start > 0 || *end < clength); }
static const char *set_max_line_length(cmd_parms *cmd, void *cfg, const char *arg) { subst_dir_conf *dcfg = (subst_dir_conf *)cfg; apr_off_t max; char *end; apr_status_t rv; rv = apr_strtoff(&max, arg, &end, 10); if (rv == APR_SUCCESS) { if ((*end == 'K' || *end == 'k') && !end[1]) { max *= KBYTE; } else if ((*end == 'M' || *end == 'm') && !end[1]) { max *= MBYTE; } else if ((*end == 'G' || *end == 'g') && !end[1]) { max *= GBYTE; } else if (*end && /* neither empty nor [Bb] */ ((*end != 'B' && *end != 'b') || end[1])) { rv = APR_EGENERAL; } } if (rv != APR_SUCCESS || max < 0) { return "SubstituteMaxLineLength must be a non-negative integer optionally " "suffixed with 'b', 'k', 'm' or 'g'."; } dcfg->max_line_length = (apr_size_t)max; dcfg->max_line_length_set = 1; return NULL; }
static const char *set_max_streaming_buffer(cmd_parms *parms, void *dummy, const char *arg) { char *err; if (apr_strtoff(&sconf->max_streaming_buffer_size, arg, &err, 10) || *err) { return "MCacheMaxStreamingBuffer value must be a number"; } return NULL; }
apr_status_t svn__strtoff(apr_off_t *offset, const char *buf, char **end, int base) { #if !APR_VERSION_AT_LEAST(1,0,0) errno = 0; *offset = strtol(buf, end, base); return APR_FROM_OS_ERROR(errno); #else return apr_strtoff(offset, buf, end, base); #endif }
static const char *set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr, const char *arg) { disk_cache_conf *conf = ap_get_module_config(parms->server->module_config, &disk_cache_module); if (apr_strtoff(&conf->maxfs, arg, NULL, 0) != APR_SUCCESS || conf->maxfs < 0) { return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes."; } return NULL; }
static apr_off_t get_start(request_rec *r) { apr_off_t start = 0; char *p = NULL; if (!r->args) { return start; } p = strstr(r->args, "start="); if (p) { p = p + 6; apr_strtoff(&start, p, NULL, 10); } return start; }
static apr_off_t get_content_length(request_rec * r) { apr_off_t len = 0; if (r->clength > 0) { return r->clength; } else if (r->main == NULL) { const char *clp = apr_table_get(r->headers_in, "Content-Length"); if (clp) { char *errp; if (apr_strtoff(&len, clp, &errp, 10) || *errp || len < 0) { len = 0; /* parse error */ } } } return len; }
static int php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) { php_struct *ctx; char *val, *ptr; ctx = SG(server_context); switch (op) { case SAPI_HEADER_DELETE: apr_table_unset(ctx->r->headers_out, sapi_header->header); return 0; case SAPI_HEADER_DELETE_ALL: apr_table_clear(ctx->r->headers_out); return 0; case SAPI_HEADER_ADD: case SAPI_HEADER_REPLACE: val = strchr(sapi_header->header, ':'); if (!val) { return 0; } ptr = val; *val = '\0'; do { val++; } while (*val == ' '); if (!strcasecmp(sapi_header->header, "content-type")) { if (ctx->content_type) { efree(ctx->content_type); } ctx->content_type = estrdup(val); } else if (!strcasecmp(sapi_header->header, "content-length")) { apr_off_t clen = 0; if (APR_SUCCESS != apr_strtoff(&clen, val, (char **) NULL, 10)) { /* We'll fall back to strtol, since that's what we used to * do anyway. */ clen = (apr_off_t) strtol(val, (char **) NULL, 10); } ap_set_content_length(ctx->r, clen); } else if (op == SAPI_HEADER_REPLACE) { apr_table_set(ctx->r->headers_out, sapi_header->header, val); } else { apr_table_add(ctx->r->headers_out, sapi_header->header, val); } *ptr = ':'; return SAPI_HEADER_ADD; default: return 0; } }
/* * main */ int main(int argc, const char * const argv[]) { apr_off_t max; apr_time_t current, repeat, delay, previous; apr_status_t status; apr_pool_t *pool, *instance; apr_getopt_t *o; apr_finfo_t info; int retries, isdaemon, limit_found, intelligent, dowork; char opt; const char *arg; char *proxypath, *path; interrupted = 0; repeat = 0; isdaemon = 0; dryrun = 0; limit_found = 0; max = 0; verbose = 0; realclean = 0; benice = 0; deldirs = 0; intelligent = 0; previous = 0; /* avoid compiler warning */ proxypath = NULL; if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) { return 1; } atexit(apr_terminate); if (argc) { shortname = apr_filepath_name_get(argv[0]); } if (apr_pool_create(&pool, NULL) != APR_SUCCESS) { return 1; } apr_pool_abort_set(oom, pool); apr_file_open_stderr(&errfile, pool); apr_signal(SIGINT, setterm); apr_signal(SIGTERM, setterm); apr_getopt_init(&o, pool, argc, argv); while (1) { status = apr_getopt(o, "iDnvrtd:l:L:p:", &opt, &arg); if (status == APR_EOF) { break; } else if (status != APR_SUCCESS) { usage(); } else { switch (opt) { case 'i': if (intelligent) { usage(); } intelligent = 1; break; case 'D': if (dryrun) { usage(); } dryrun = 1; break; case 'n': if (benice) { usage(); } benice = 1; break; case 't': if (deldirs) { usage(); } deldirs = 1; break; case 'v': if (verbose) { usage(); } verbose = 1; break; case 'r': if (realclean) { usage(); } realclean = 1; deldirs = 1; break; case 'd': if (isdaemon) { usage(); } isdaemon = 1; repeat = apr_atoi64(arg); repeat *= SECS_PER_MIN; repeat *= APR_USEC_PER_SEC; break; case 'l': if (limit_found) { usage(); } limit_found = 1; do { apr_status_t rv; char *end; rv = apr_strtoff(&max, arg, &end, 10); if (rv == APR_SUCCESS) { if ((*end == 'K' || *end == 'k') && !end[1]) { max *= KBYTE; } else if ((*end == 'M' || *end == 'm') && !end[1]) { max *= MBYTE; } else if ((*end == 'G' || *end == 'g') && !end[1]) { max *= GBYTE; } else if (*end && /* neither empty nor [Bb] */ ((*end != 'B' && *end != 'b') || end[1])) { rv = APR_EGENERAL; } } if (rv != APR_SUCCESS) { apr_file_printf(errfile, "Invalid limit: %s" APR_EOL_STR APR_EOL_STR, arg); usage(); } } while(0); break; case 'p': if (proxypath) { usage(); } proxypath = apr_pstrdup(pool, arg); if (apr_filepath_set(proxypath, pool) != APR_SUCCESS) { usage(); } break; } /* switch */ } /* else */ } /* while */ if (o->ind != argc) { usage(); } if (isdaemon && (repeat <= 0 || verbose || realclean || dryrun)) { usage(); } if (!isdaemon && intelligent) { usage(); } if (!proxypath || max <= 0) { usage(); } if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) { usage(); } baselen = strlen(path); #ifndef DEBUG if (isdaemon) { apr_file_close(errfile); apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); } #endif do { apr_pool_create(&instance, pool); now = apr_time_now(); APR_RING_INIT(&root, _entry, link); delcount = 0; unsolicited = 0; dowork = 0; switch (intelligent) { case 0: dowork = 1; break; case 1: retries = STAT_ATTEMPTS; status = APR_SUCCESS; do { if (status != APR_SUCCESS) { apr_sleep(STAT_DELAY); } status = apr_stat(&info, path, APR_FINFO_MTIME, instance); } while (status != APR_SUCCESS && !interrupted && --retries); if (status == APR_SUCCESS) { previous = info.mtime; intelligent = 2; } dowork = 1; break; case 2: retries = STAT_ATTEMPTS; status = APR_SUCCESS; do { if (status != APR_SUCCESS) { apr_sleep(STAT_DELAY); } status = apr_stat(&info, path, APR_FINFO_MTIME, instance); } while (status != APR_SUCCESS && !interrupted && --retries); if (status == APR_SUCCESS) { if (previous != info.mtime) { dowork = 1; } previous = info.mtime; break; } intelligent = 1; dowork = 1; break; } if (dowork && !interrupted) { if (!process_dir(path, instance) && !interrupted) { purge(path, instance, max); } else if (!isdaemon && !interrupted) { apr_file_printf(errfile, "An error occurred, cache cleaning " "aborted." APR_EOL_STR); return 1; } if (intelligent && !interrupted) { retries = STAT_ATTEMPTS; status = APR_SUCCESS; do { if (status != APR_SUCCESS) { apr_sleep(STAT_DELAY); } status = apr_stat(&info, path, APR_FINFO_MTIME, instance); } while (status != APR_SUCCESS && !interrupted && --retries); if (status == APR_SUCCESS) { previous = info.mtime; intelligent = 2; } else { intelligent = 1; } } } apr_pool_destroy(instance); current = apr_time_now(); if (current < now) { delay = repeat; } else if (current - now >= repeat) { delay = repeat; } else { delay = now + repeat - current; } /* we can't sleep the whole delay time here apiece as this is racy * with respect to interrupt delivery - think about what happens * if we have tested for an interrupt, then get scheduled * before the apr_sleep() call and while waiting for the cpu * we do get an interrupt */ if (isdaemon) { while (delay && !interrupted) { if (delay > APR_USEC_PER_SEC) { apr_sleep(APR_USEC_PER_SEC); delay -= APR_USEC_PER_SEC; } else { apr_sleep(delay); delay = 0; } } } } while (isdaemon && !interrupted); if (!isdaemon && interrupted) { apr_file_printf(errfile, "Cache cleaning aborted due to user " "request." APR_EOL_STR); return 1; } return 0; }
static int ap_set_byterange(request_rec *r, apr_off_t clength, apr_array_header_t **indexes) { const char *range; const char *if_range; const char *match; const char *ct; char *cur; int num_ranges = 0, unsatisfiable = 0; apr_off_t sum_lengths = 0; indexes_t *idx; int ranges = 1; const char *it; if (r->assbackwards) { return 0; } /* * Check for Range request-header (HTTP/1.1) or Request-Range for * backwards-compatibility with second-draft Luotonen/Franks * byte-ranges (e.g. Netscape Navigator 2-3). * * We support this form, with Request-Range, and (farther down) we * send multipart/x-byteranges instead of multipart/byteranges for * Request-Range based requests to work around a bug in Netscape * Navigator 2-3 and MSIE 3. */ if (!(range = apr_table_get(r->headers_in, "Range"))) { range = apr_table_get(r->headers_in, "Request-Range"); } if (!range || strncasecmp(range, "bytes=", 6) || r->status != HTTP_OK) { return 0; } /* is content already a single range? */ if (apr_table_get(r->headers_out, "Content-Range")) { return 0; } /* is content already a multiple range? */ if ((ct = apr_table_get(r->headers_out, "Content-Type")) && (!strncasecmp(ct, "multipart/byteranges", 20) || !strncasecmp(ct, "multipart/x-byteranges", 22))) { return 0; } /* * Check the If-Range header for Etag or Date. * Note that this check will return false (as required) if either * of the two etags are weak. */ if ((if_range = apr_table_get(r->headers_in, "If-Range"))) { if (if_range[0] == '"') { if (!(match = apr_table_get(r->headers_out, "Etag")) || (strcmp(if_range, match) != 0)) { return 0; } } else if (!(match = apr_table_get(r->headers_out, "Last-Modified")) || (strcmp(if_range, match) != 0)) { return 0; } } range += 6; it = range; while (*it) { if (*it++ == ',') { ranges++; } } it = range; *indexes = apr_array_make(r->pool, ranges, sizeof(indexes_t)); while ((cur = ap_getword(r->pool, &range, ','))) { char *dash; char *errp; apr_off_t number, start, end; if (!*cur) break; /* * Per RFC 2616 14.35.1: If there is at least one syntactically invalid * byte-range-spec, we must ignore the whole header. */ if (!(dash = strchr(cur, '-'))) { return 0; } if (dash == cur) { /* In the form "-5" */ if (apr_strtoff(&number, dash+1, &errp, 10) || *errp) { return 0; } if (number < 1) { return 0; } start = clength - number; end = clength - 1; } else { *dash++ = '\0'; if (apr_strtoff(&number, cur, &errp, 10) || *errp) { return 0; } start = number; if (*dash) { if (apr_strtoff(&number, dash, &errp, 10) || *errp) { return 0; } end = number; if (start > end) { return 0; } } else { /* "5-" */ end = clength - 1; /* * special case: 0- * ignore all other ranges provided * return as a single range: 0- */ if (start == 0) { (*indexes)->nelts = 0; idx = (indexes_t *)apr_array_push(*indexes); idx->start = start; idx->end = end; sum_lengths = clength; num_ranges = 1; break; } } } if (start < 0) { start = 0; } if (start >= clength) { unsatisfiable = 1; continue; } if (end >= clength) { end = clength - 1; } idx = (indexes_t *)apr_array_push(*indexes); idx->start = start; idx->end = end; sum_lengths += end - start + 1; /* new set again */ num_ranges++; } if (num_ranges == 0 && unsatisfiable) { /* If all ranges are unsatisfiable, we should return 416 */ return -1; } if (sum_lengths > clength) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Sum of ranges larger than file, ignoring."); return 0; } r->status = HTTP_PARTIAL_CONTENT; r->range = it; return num_ranges; }
long long fend_to_number(char* str) { apr_off_t result = 0; apr_strtoff(&result, str, NULL, 0); return result; }
static int reflector_handler(request_rec * r) { apr_bucket_brigade *bbin, *bbout; reflector_cfg *conf; apr_status_t status; if (strcmp(r->handler, "reflector")) { return DECLINED; } conf = (reflector_cfg *) ap_get_module_config(r->per_dir_config, &reflector_module); ap_allow_methods(r, 1, "POST", "OPTIONS", NULL); if (r->method_number == M_OPTIONS) { return ap_send_http_options(r); } else if (r->method_number == M_POST) { const char *content_length, *content_type; int seen_eos; /* * Sometimes we'll get in a state where the input handling has * detected an error where we want to drop the connection, so if * that's the case, don't read the data as that is what we're trying * to avoid. * * This function is also a no-op on a subrequest. */ if (r->main || r->connection->keepalive == AP_CONN_CLOSE || ap_status_drops_connection(r->status)) { return OK; } /* copy headers from in to out if configured */ apr_table_do(header_do, r, conf->headers, NULL); /* last modified defaults to now, unless otherwise set on the way in */ if (!apr_table_get(r->headers_out, "Last-Modified")) { ap_update_mtime(r, apr_time_now()); ap_set_last_modified(r); } ap_set_accept_ranges(r); /* reflect the content length, if present */ if ((content_length = apr_table_get(r->headers_in, "Content-Length"))) { apr_off_t offset; apr_strtoff(&offset, content_length, NULL, 10); ap_set_content_length(r, offset); } /* reflect the content type, if present */ if ((content_type = apr_table_get(r->headers_in, "Content-Type"))) { ap_set_content_type(r, content_type); } bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc); bbout = apr_brigade_create(r->pool, r->connection->bucket_alloc); seen_eos = 0; do { apr_bucket *bucket; status = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES, APR_BLOCK_READ, HUGE_STRING_LEN); if (status != APR_SUCCESS) { if (status == AP_FILTER_ERROR) { apr_brigade_destroy(bbin); return status; } else { apr_brigade_destroy(bbin); return HTTP_BAD_REQUEST; } } for (bucket = APR_BRIGADE_FIRST(bbin); bucket != APR_BRIGADE_SENTINEL(bbin); bucket = APR_BUCKET_NEXT(bucket)) { const char *data; apr_size_t len; if (APR_BUCKET_IS_EOS(bucket)) { seen_eos = 1; break; } /* These are metadata buckets. */ if (bucket->length == 0) { continue; } /* * We MUST read because in case we have an unknown-length * bucket or one that morphs, we want to exhaust it. */ status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); if (status != APR_SUCCESS) { apr_brigade_destroy(bbin); return HTTP_BAD_REQUEST; } apr_brigade_write(bbout, NULL, NULL, data, len); status = ap_pass_brigade(r->output_filters, bbout); if (status != APR_SUCCESS) { /* no way to know what type of error occurred */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(01410) "reflector_handler: ap_pass_brigade returned %i", status); return HTTP_INTERNAL_SERVER_ERROR; } } apr_brigade_cleanup(bbin); } while (!seen_eos); return OK; } else { return HTTP_METHOD_NOT_ALLOWED; } }
/** * This is the KEEP_BODY_INPUT filter for HTTP requests, for times when the * body should be set aside for future use by other modules. */ static apr_status_t keep_body_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { apr_bucket *e; keep_body_ctx_t *ctx = f->ctx; apr_status_t rv; apr_bucket *bucket; apr_off_t len = 0; if (!ctx) { const char *lenp; char *endstr = NULL; request_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config, &request_module); /* must we step out of the way? */ if (!dconf->keep_body || f->r->kept_body) { ap_remove_input_filter(f); return ap_get_brigade(f->next, b, mode, block, readbytes); } f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); /* fail fast if the content length exceeds keep body */ lenp = apr_table_get(f->r->headers_in, "Content-Length"); if (lenp) { /* Protects against over/underflow, non-digit chars in the * string (excluding leading space) (the endstr checks) * and a negative number. */ if (apr_strtoff(&ctx->remaining, lenp, &endstr, 10) || endstr == lenp || *endstr || ctx->remaining < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01411) "Invalid Content-Length"); ap_remove_input_filter(f); return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE); } /* If we have a limit in effect and we know the C-L ahead of * time, stop it here if it is invalid. */ if (dconf->keep_body < ctx->remaining) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01412) "Requested content-length of %" APR_OFF_T_FMT " is larger than the configured limit" " of %" APR_OFF_T_FMT, ctx->remaining, dconf->keep_body); ap_remove_input_filter(f); return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE); } } f->r->kept_body = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); ctx->remaining = dconf->keep_body; } /* get the brigade from upstream, and read it in to get its length */ rv = ap_get_brigade(f->next, b, mode, block, readbytes); if (rv == APR_SUCCESS) { rv = apr_brigade_length(b, 1, &len); } /* does the length take us over the limit? */ if (APR_SUCCESS == rv && len > ctx->remaining) { if (f->r->kept_body) { apr_brigade_cleanup(f->r->kept_body); f->r->kept_body = NULL; } ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01413) "Requested content-length of %" APR_OFF_T_FMT " is larger than the configured limit" " of %" APR_OFF_T_FMT, len, ctx->keep_body); return bail_out_on_error(b, f, HTTP_REQUEST_ENTITY_TOO_LARGE); } ctx->remaining -= len; /* pass any errors downstream */ if (rv != APR_SUCCESS) { if (f->r->kept_body) { apr_brigade_cleanup(f->r->kept_body); f->r->kept_body = NULL; } return rv; } /* all is well, set aside the buckets */ for (bucket = APR_BRIGADE_FIRST(b); bucket != APR_BRIGADE_SENTINEL(b); bucket = APR_BUCKET_NEXT(bucket)) { apr_bucket_copy(bucket, &e); APR_BRIGADE_INSERT_TAIL(f->r->kept_body, e); } return APR_SUCCESS; }
/** * Parse the Cache-Control and Pragma headers in one go, marking * which tokens appear within the header. Populate the structure * passed in. */ int ap_cache_control(request_rec *r, cache_control_t *cc, const char *cc_header, const char *pragma_header, apr_table_t *headers) { char *last; if (cc->parsed) { return cc->cache_control || cc->pragma; } cc->parsed = 1; cc->max_age_value = -1; cc->max_stale_value = -1; cc->min_fresh_value = -1; cc->s_maxage_value = -1; if (pragma_header) { char *header = apr_pstrdup(r->pool, pragma_header); const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); while (token) { /* handle most common quickest case... */ if (!strcmp(token, "no-cache")) { cc->no_cache = 1; } /* ...then try slowest case */ else if (!strcasecmp(token, "no-cache")) { cc->no_cache = 1; } token = cache_strqtok(NULL, CACHE_SEPARATOR, &last); } cc->pragma = 1; } if (cc_header) { char *endp; apr_off_t offt; char *header = apr_pstrdup(r->pool, cc_header); const char *token = cache_strqtok(header, CACHE_SEPARATOR, &last); while (token) { switch (token[0]) { case 'n': case 'N': { /* handle most common quickest cases... */ if (!strcmp(token, "no-cache")) { cc->no_cache = 1; } else if (!strcmp(token, "no-store")) { cc->no_store = 1; } /* ...then try slowest cases */ else if (!strncasecmp(token, "no-cache", 8)) { if (token[8] == '=') { cc->no_cache_header = 1; } else if (!token[8]) { cc->no_cache = 1; } break; } else if (!strcasecmp(token, "no-store")) { cc->no_store = 1; } else if (!strcasecmp(token, "no-transform")) { cc->no_transform = 1; } break; } case 'm': case 'M': { /* handle most common quickest cases... */ if (!strcmp(token, "max-age=0")) { cc->max_age = 1; cc->max_age_value = 0; } else if (!strcmp(token, "must-revalidate")) { cc->must_revalidate = 1; } /* ...then try slowest cases */ else if (!strncasecmp(token, "max-age", 7)) { if (token[7] == '=' && !apr_strtoff(&offt, token + 8, &endp, 10) && endp > token + 8 && !*endp) { cc->max_age = 1; cc->max_age_value = offt; } break; } else if (!strncasecmp(token, "max-stale", 9)) { if (token[9] == '=' && !apr_strtoff(&offt, token + 10, &endp, 10) && endp > token + 10 && !*endp) { cc->max_stale = 1; cc->max_stale_value = offt; } else if (!token[9]) { cc->max_stale = 1; cc->max_stale_value = -1; } break; } else if (!strncasecmp(token, "min-fresh", 9)) { if (token[9] == '=' && !apr_strtoff(&offt, token + 10, &endp, 10) && endp > token + 10 && !*endp) { cc->min_fresh = 1; cc->min_fresh_value = offt; } break; } else if (!strcasecmp(token, "must-revalidate")) { cc->must_revalidate = 1; } break; } case 'o': case 'O': { if (!strcasecmp(token, "only-if-cached")) { cc->only_if_cached = 1; } break; } case 'p': case 'P': { /* handle most common quickest cases... */ if (!strcmp(token, "private")) { cc->private = 1; } /* ...then try slowest cases */ else if (!strcasecmp(token, "public")) { cc->public = 1; } else if (!strncasecmp(token, "private", 7)) { if (token[7] == '=') { cc->private_header = 1; } else if (!token[7]) { cc->private = 1; } break; }
int cache_check_freshness(cache_handle_t *h, cache_request_rec *cache, request_rec *r) { apr_status_t status; apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale; apr_int64_t minfresh; const char *cc_req; const char *pragma; const char *agestr = NULL; apr_time_t age_c = 0; cache_info *info = &(h->cache_obj->info); const char *warn_head; cache_server_conf *conf = (cache_server_conf *)ap_get_module_config(r->server->module_config, &cache_module); /* * We now want to check if our cached data is still fresh. This depends * on a few things, in this order: * * - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache. no-cache * in either the request or the cached response means that we must * perform the request unconditionally, and ignore cached content. We * should never reach here, but if we do, mark the content as stale, * as this is the best we can do. * * - RFC2616 14.32 Pragma: no-cache This is treated the same as * Cache-Control: no-cache. * * - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate, * proxy-revalidate if the max-stale request header exists, modify the * stale calculations below so that an object can be at most <max-stale> * seconds stale before we request a revalidation, _UNLESS_ a * must-revalidate or proxy-revalidate cached response header exists to * stop us doing this. * * - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the * maximum age an object can be before it is considered stale. This * directive has the effect of proxy|must revalidate, which in turn means * simple ignore any max-stale setting. * * - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both * requests and responses. If both are specified, the smaller of the two * takes priority. * * - RFC2616 14.21 Expires: if this request header exists in the cached * entity, and it's value is in the past, it has expired. * */ /* This value comes from the client's initial request. */ cc_req = apr_table_get(r->headers_in, "Cache-Control"); pragma = apr_table_get(r->headers_in, "Pragma"); ap_cache_control(r, &cache->control_in, cc_req, pragma, r->headers_in); if (cache->control_in.no_cache) { if (!conf->ignorecachecontrol) { /* Treat as stale, causing revalidation */ return 0; } ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00781) "Incoming request is asking for a uncached version of " "%s, but we have been configured to ignore it and " "serve a cached response anyway", r->unparsed_uri); } /* These come from the cached entity. */ if (h->cache_obj->info.control.no_cache || h->cache_obj->info.control.invalidated) { /* * The cached entity contained Cache-Control: no-cache, or a * no-cache with a header present, or a private with a header * present, or the cached entity has been invalidated in the * past, so treat as stale causing revalidation. */ return 0; } if ((agestr = apr_table_get(h->resp_hdrs, "Age"))) { char *endp; apr_off_t offt; if (!apr_strtoff(&offt, agestr, &endp, 10) && endp > agestr && !*endp) { age_c = offt; } } /* calculate age of object */ age = ap_cache_current_age(info, age_c, r->request_time); /* extract s-maxage */ smaxage = h->cache_obj->info.control.s_maxage_value; /* extract max-age from request */ maxage_req = -1; if (!conf->ignorecachecontrol) { maxage_req = cache->control_in.max_age_value; } /* * extract max-age from response, if both s-maxage and max-age, s-maxage * takes priority */ if (smaxage != -1) { maxage_cresp = smaxage; } else { maxage_cresp = h->cache_obj->info.control.max_age_value; } /* * if both maxage request and response, the smaller one takes priority */ if (maxage_req == -1) { maxage = maxage_cresp; } else if (maxage_cresp == -1) { maxage = maxage_req; } else { maxage = MIN(maxage_req, maxage_cresp); } /* extract max-stale */ if (cache->control_in.max_stale) { if(cache->control_in.max_stale_value != -1) { maxstale = cache->control_in.max_stale_value; } else { /* * If no value is assigned to max-stale, then the client is willing * to accept a stale response of any age (RFC2616 14.9.3). We will * set it to one year in this case as this situation is somewhat * similar to a "never expires" Expires header (RFC2616 14.21) * which is set to a date one year from the time the response is * sent in this case. */ maxstale = APR_INT64_C(86400*365); } } else { maxstale = 0; } /* extract min-fresh */ if (!conf->ignorecachecontrol && cache->control_in.min_fresh) { minfresh = cache->control_in.min_fresh_value; } else { minfresh = 0; } /* override maxstale if must-revalidate, proxy-revalidate or s-maxage */ if (maxstale && (h->cache_obj->info.control.must_revalidate || h->cache_obj->info.control.proxy_revalidate || smaxage != -1)) { maxstale = 0; } /* handle expiration */ if (((maxage != -1) && (age < (maxage + maxstale - minfresh))) || ((smaxage == -1) && (maxage == -1) && (info->expire != APR_DATE_BAD) && (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) { warn_head = apr_table_get(h->resp_hdrs, "Warning"); /* it's fresh darlings... */ /* set age header on response */ apr_table_set(h->resp_hdrs, "Age", apr_psprintf(r->pool, "%lu", (unsigned long)age)); /* add warning if maxstale overrode freshness calculation */ if (!(((maxage != -1) && age < maxage) || (info->expire != APR_DATE_BAD && (apr_time_sec(info->expire - info->date)) > age))) { /* make sure we don't stomp on a previous warning */ if ((warn_head == NULL) || ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) { apr_table_mergen(h->resp_hdrs, "Warning", "110 Response is stale"); } } /* * If none of Expires, Cache-Control: max-age, or Cache-Control: * s-maxage appears in the response, and the response header age * calculated is more than 24 hours add the warning 113 */ if ((maxage_cresp == -1) && (smaxage == -1) && (apr_table_get( h->resp_hdrs, "Expires") == NULL) && (age > 86400)) { /* Make sure we don't stomp on a previous warning, and don't dup * a 113 marning that is already present. Also, make sure to add * the new warning to the correct *headers_out location. */ if ((warn_head == NULL) || ((warn_head != NULL) && (ap_strstr_c(warn_head, "113") == NULL))) { apr_table_mergen(h->resp_hdrs, "Warning", "113 Heuristic expiration"); } } return 1; /* Cache object is fresh (enough) */ } /* * At this point we are stale, but: if we are under load, we may let * a significant number of stale requests through before the first * stale request successfully revalidates itself, causing a sudden * unexpected thundering herd which in turn brings angst and drama. * * So. * * We want the first stale request to go through as normal. But the * second and subsequent request, we must pretend to be fresh until * the first request comes back with either new content or confirmation * that the stale content is still fresh. * * To achieve this, we create a very simple file based lock based on * the key of the cached object. We attempt to open the lock file with * exclusive write access. If we succeed, woohoo! we're first, and we * follow the stale path to the backend server. If we fail, oh well, * we follow the fresh path, and avoid being a thundering herd. * * The lock lives only as long as the stale request that went on ahead. * If the request succeeds, the lock is deleted. If the request fails, * the lock is deleted, and another request gets to make a new lock * and try again. * * At any time, a request marked "no-cache" will force a refresh, * ignoring the lock, ensuring an extended lockout is impossible. * * A lock that exceeds a maximum age will be deleted, and another * request gets to make a new lock and try again. */ status = cache_try_lock(conf, cache, r); if (APR_SUCCESS == status) { /* we obtained a lock, follow the stale path */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00782) "Cache lock obtained for stale cached URL, " "revalidating entry: %s", r->unparsed_uri); return 0; } else if (APR_STATUS_IS_EEXIST(status)) { /* lock already exists, return stale data anyway, with a warning */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00783) "Cache already locked for stale cached URL, " "pretend it is fresh: %s", r->unparsed_uri); /* make sure we don't stomp on a previous warning */ warn_head = apr_table_get(h->resp_hdrs, "Warning"); if ((warn_head == NULL) || ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) { apr_table_mergen(h->resp_hdrs, "Warning", "110 Response is stale"); } return 1; } else { /* some other error occurred, just treat the object as stale */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(00784) "Attempt to obtain a cache lock for stale " "cached URL failed, revalidating entry anyway: %s", r->unparsed_uri); return 0; } }