@@ -212,6 +212,11 @@ typedef struct {
212212 apr_table_t * expiresbytype ;
213213} expires_dir_config ;
214214
215+ typedef struct {
216+ int defaulted ;
217+ apr_table_t * expfields ;
218+ } expires_interphase_t ;
219+
215220/* from mod_dir, why is this alias used?
216221 */
217222#define DIR_CMD_PERMS OR_INDEXES
@@ -416,59 +421,23 @@ static void *merge_expires_dir_configs(apr_pool_t *p, void *basev, void *addv)
416421 return new ;
417422}
418423
419- static int add_expires (request_rec * r )
424+ /*
425+ * Handle the setting of the expiration response header fields according
426+ * to our criteria.
427+ */
428+
429+ static int set_expiration_fields (request_rec * r , const char * code ,
430+ apr_table_t * t )
420431{
421- expires_dir_config * conf ;
422- char * code ;
423432 apr_time_t base ;
424433 apr_time_t additional ;
425434 apr_time_t expires ;
426435 int additional_sec ;
427436 char * timestr ;
437+ expires_interphase_t * notes ;
428438
429- if (ap_is_HTTP_ERROR (r -> status )) /* Don't add Expires headers to errors */
430- return DECLINED ;
431-
432- if (r -> main != NULL ) /* Say no to subrequests */
433- return DECLINED ;
434-
435- conf = (expires_dir_config * ) ap_get_module_config (r -> per_dir_config , & expires_module );
436- if (conf == NULL ) {
437- ap_log_rerror (APLOG_MARK , APLOG_ERR , 0 , r ,
438- "internal error: %s" , r -> filename );
439- return HTTP_INTERNAL_SERVER_ERROR ;
440- }
441-
442- if (conf -> active != ACTIVE_ON )
443- return DECLINED ;
444-
445- /* we perhaps could use the default_type(r) in its place but that
446- * may be 2nd guesing the desired configuration... calling table_get
447- * with a NULL key will SEGV us
448- *
449- * I still don't know *why* r->content_type would ever be NULL, this
450- * is possibly a result of fixups being called in many different
451- * places. Fixups is probably the wrong place to be doing all this
452- * work... Bah.
453- *
454- * Changed as of 08.Jun.96 don't DECLINE, look for an ExpiresDefault.
455- */
456- if (r -> content_type == NULL )
457- code = NULL ;
458- else
459- code = (char * ) apr_table_get (conf -> expiresbytype ,
460- ap_field_noparam (r -> pool , r -> content_type ));
461-
462- if (code == NULL ) {
463- /* no expires defined for that type, is there a default? */
464- code = conf -> expiresdefault ;
465-
466- if (code [0 ] == '\0' )
467- return OK ;
468- }
469-
470- /* we have our code */
471-
439+ notes = (expires_interphase_t * ) ap_get_module_config (r -> request_config ,
440+ & expires_module );
472441 switch (code [0 ]) {
473442 case 'M' :
474443 if (r -> finfo .filetype == 0 ) {
@@ -499,17 +468,143 @@ static int add_expires(request_rec *r)
499468 }
500469
501470 expires = base + additional ;
502- apr_table_mergen (r -> headers_out , "Cache-Control" ,
503- apr_psprintf (r -> pool , "max-age=%" APR_TIME_T_FMT ,
504- apr_time_sec (expires - r -> request_time )));
471+ apr_table_mergen (t , "Cache-Control" ,
472+ apr_psprintf (r -> pool , "max-age=%" APR_TIME_T_FMT ,
473+ apr_time_sec (expires - r -> request_time )));
505474 timestr = apr_palloc (r -> pool , APR_RFC822_DATE_LEN );
506475 apr_rfc822_date (timestr , expires );
507- apr_table_setn (r -> headers_out , "Expires" , timestr );
476+ apr_table_setn (t , "Expires" , timestr );
508477 return OK ;
509478}
510479
480+ /*
481+ * Output filter to set the Expires response header field
482+ * according to the content-type of the response -- if it hasn't
483+ * already been set.
484+ */
485+ static apr_status_t expires_by_type_filter (ap_filter_t * f ,
486+ apr_bucket_brigade * b )
487+ {
488+ request_rec * r ;
489+ expires_dir_config * conf ;
490+ expires_interphase_t * notes ;
491+ const char * bytype_expiry ;
492+ apr_table_t * t ;
493+
494+ r = f -> r ;
495+ conf = (expires_dir_config * ) ap_get_module_config (r -> per_dir_config ,
496+ & expires_module );
497+ notes = (expires_interphase_t * ) ap_get_module_config (r -> request_config ,
498+ & expires_module );
499+
500+ /*
501+ * If this filter is getting called, it *should* mean that
502+ * the fixup-phase handler ran and set things up for us.
503+ * Check to see which output header table we should use;
504+ * mod_cgi loads script fields into r->err_headers_out,
505+ * for instance.
506+ */
507+ bytype_expiry = apr_table_get (r -> err_headers_out , "Expires" );
508+ if (bytype_expiry != NULL ) {
509+ t = r -> err_headers_out ;
510+ }
511+ else {
512+ bytype_expiry = apr_table_get (r -> headers_out , "Expires" );
513+ t = r -> headers_out ;
514+ }
515+ if (bytype_expiry == NULL ) {
516+ /*
517+ * No expiration has been set, so we can apply any managed by
518+ * this module. Check for one for this content type.
519+ */
520+ bytype_expiry = apr_table_get (conf -> expiresbytype ,
521+ ap_field_noparam (r -> pool ,
522+ r -> content_type ));
523+ if (bytype_expiry != NULL ) {
524+ set_expiration_fields (r , bytype_expiry , t );
525+ }
526+ else if ((notes != NULL ) && notes -> defaulted ) {
527+ /*
528+ * None for this type, but there was a default defined --
529+ * so use it.
530+ */
531+ t = apr_table_overlay (r -> pool , notes -> expfields , t );
532+ }
533+ }
534+ ap_remove_output_filter (f );
535+ return ap_pass_brigade (f -> next , b );
536+ }
537+
538+ static int add_expires (request_rec * r )
539+ {
540+ expires_dir_config * conf ;
541+ expires_interphase_t * notes ;
542+ apr_table_t * rfields ;
543+ char * code ;
544+
545+ if (ap_is_HTTP_ERROR (r -> status )) {/* Don't add Expires headers to errors */
546+ return DECLINED ;
547+ }
548+
549+ if (r -> main != NULL ) { /* Say no to subrequests */
550+ return DECLINED ;
551+ }
552+
553+ conf = (expires_dir_config * ) ap_get_module_config (r -> per_dir_config ,
554+ & expires_module );
555+ if (conf == NULL ) {
556+ ap_log_rerror (APLOG_MARK , APLOG_ERR , 0 , r ,
557+ "internal error: %s" , r -> filename );
558+ return HTTP_INTERNAL_SERVER_ERROR ;
559+ }
560+
561+ if (conf -> active != ACTIVE_ON ) {
562+ return DECLINED ;
563+ }
564+
565+ notes = apr_palloc (r -> pool , sizeof (expires_interphase_t ));
566+ notes -> defaulted = 0 ;
567+ notes -> expfields = apr_table_make (r -> pool , 4 );
568+ ap_set_module_config (r -> request_config , & expires_module , notes );
569+
570+ /*
571+ * If there are any ExpiresByType directives for this scope,
572+ * add the output filter and defer all setting to it. We
573+ * do make a note of any ExpiresDefault value for its use.
574+ */
575+ if (! apr_is_empty_table (conf -> expiresbytype )) {
576+ ap_add_output_filter ("EXPIRATION" , NULL , r , r -> connection );
577+ rfields = notes -> expfields ;
578+ }
579+ else {
580+ rfields = r -> headers_out ;
581+ }
582+ /*
583+ * Apply the default expiration if there is one; the filter will
584+ * narrow it down later if possible.
585+ */
586+ code = conf -> expiresdefault ;
587+
588+ if (code [0 ] == '\0' ) {
589+ return OK ;
590+ }
591+ else {
592+ /*
593+ * Note that we're setting it from the default, so that
594+ * the output filter (if it runs) knows it can override the
595+ * value. This allows the by-type filter to be able to
596+ * tell the difference between a value set by, say, a
597+ * CGI script and the one we set by default.
598+ */
599+ notes -> defaulted = 1 ;
600+ }
601+ return set_expiration_fields (r , code , rfields );
602+ }
603+
511604static void register_hooks (apr_pool_t * p )
512605{
606+ ap_register_output_filter ("EXPIRATION" , expires_by_type_filter , NULL ,
607+ AP_FTYPE_CONTENT_SET );
513608 ap_hook_fixups (add_expires ,NULL ,NULL ,APR_HOOK_MIDDLE );
514609}
515610
0 commit comments