/** * * Copyright (C) 2007 Alex Samad (Alex@Samad.com.au) This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package au.com.samad.java.jspwiki.plugin; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.regex.Pattern; import org.apache.log4j.Logger; import com.ecyrd.jspwiki.TextUtil; import com.ecyrd.jspwiki.WikiContext; import com.ecyrd.jspwiki.plugin.PluginException; import com.ecyrd.jspwiki.plugin.WikiPlugin; import com.ecyrd.jspwiki.ui.TemplateManager; /** * Plugin HTTPHeader controls HTTP Header information associated with a Wiki * Page
*
* This plugin adds a resource to the pages, it adds nothing to the HTML. You * can have multiple entries on a pages. The input paramaters are Header and * Value
*
* Header - is the HTTP Header as defined in rfc2616
* Value - is the value to assing to the Header, NULL is acceptable in some * circumstances and a default value will be used
*
*
* ftp://ftp.isi.edu/in-notes/rfc2616.txt Section 4.2 + 4.5
* The order in which header fields with differing field names are received is * not significant. However, it is "good practice" to send general-header fields * first, followed by request-header or response- header fields, and ending with * the entity-header fields.
* Process these headers
*
*
* General Header Fields
*
  • Cache-Control ; Section 14.9
  • *
  • Date ; Section 14.18
  • *
  • Pragma ; Section 14.32
  • *
    *
    * Response Header Fields
    *
  • Age ; Section 14.6
  • *
  • ETag ; Section 14.19
  • *
    * Advance features can be control in jspwiki.properties
    * jspwiki.plugin.HTTPHeader.Adv
    *
  • ON : allow advanced features - allows for any header to be set *
  • OFF : Only allows the basic features Cache-Control, Date, Pragma, Age, * ETag
    * Default is off * * @author Alex Samad * @version 0.1 */ public class HTTPHeader implements WikiPlugin { /** * Property for HTTPHeader
    * set in jspwiki.properties
    * jspwiki.plugin.HTTPHeader.Adv
    *
  • ON : allow advanced features - allows for any header to be set *
  • OFF : Only allows the basic features Cache-Control, Date, Pragma, * Age, ETag
    * Default is off */ public static final String PROP_HTTPheader_ADV = "jspwiki.plugin.HTTPHeader.Adv"; /* * Flag to allow Advanced features */ private static Boolean m_Advanced = null;; /* * Access to the system logger */ private static Logger log = Logger.getLogger(HTTPHeader.class); /** * The Header being defined */ public static final String PARAM_HEADER = "header"; /** * The Value associated with the Header */ public static final String PARAM_VALUE = "value"; /** * Gets you access to the advanced features
    * 0 - no advanced features (default)
    * 1 - Allow any extra headers and values (Still check the known ones)
    * 2 - Allow any header with any values - no checking done
    * Only document this in the code not for general consumption */ public static final String PARAM_ADVANCED = "advanced"; /** * Cache Control */ public static final String HEADER_CACHE_CONTROL = "Cache-Control"; /** * Date Header */ public static final String HEADER_DATE = "Date"; /** * Pragma Header */ public static final String HEADER_PRAGMA = "Pragma"; /** * Age header */ public static final String HEADER_AGE = "Age"; /** * ETag header */ public static final String HEADER_ETAG = "ETag"; /** * @see com.ecyrd.jspwiki.plugin.WikiPlugin * @param context * @param params * @throws PluginException * @return String */ public String execute(WikiContext context, Map params) throws PluginException { log.debug("Process HTTPHeader plugin"); if (!params.containsKey(PARAM_HEADER)) { // PARAM_HEADER not defined log.info("HTTPHeader called with out " + PARAM_HEADER + " Parameter plugin ignored"); return null; } String header = ((String) params.get(PARAM_HEADER)).replaceAll("^ *", "").replaceAll(" *$", ""); String value = null; if (params.containsKey(PARAM_VALUE)) { // Trim white space at begging &Trim white space at end value = ((String) params.get(PARAM_VALUE)).replaceAll("^ *", "") .replaceAll(" *$", ""); } if (m_Advanced == null) { // Initial advance flag based on value in wikiengine properties file // If its not set to the value ON then its off m_Advanced = new Boolean(TextUtil.getStringProperty( context.getEngine().getWikiProperties(), PROP_HTTPheader_ADV, null).compareToIgnoreCase("ON") == 0); } final int ADVANCED_NO = 0; final int ADVANCED_EXTRA = 1; final int ADVANCED_PLUS = 2; int advanced = ADVANCED_NO; if (m_Advanced.booleanValue() && params.containsKey(PARAM_ADVANCED)) { advanced = Integer.parseInt((String) params.get(PARAM_ADVANCED)); log.info("HTTPHeader call with Advanced (" + advanced + ") param " + value == null ? "no value" : "[" + value + "]"); } /* * Big String Switch Statement */ if (m_Advanced.booleanValue() && advanced == ADVANCED_PLUS) { TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, header + ":" + value); } else if (header.equalsIgnoreCase(HEADER_CACHE_CONTROL)) { processCacheControl(context, value); } else if (header.equalsIgnoreCase(HEADER_DATE)) { processDate(context, value); } else if (header.equalsIgnoreCase(HEADER_PRAGMA)) { processPragma(context, value); } else if (header.equalsIgnoreCase(HEADER_AGE)) { processAge(context, value); } else if (header.equalsIgnoreCase(HEADER_ETAG)) { processETag(context, value); } else if (m_Advanced.booleanValue() && advanced == ADVANCED_EXTRA) { TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, header + ":" + value); } else { log.info("HTTP Header called with unknown header" + header); } return null; } /** *
  • Cache-Control ; Section 14.9
  • * Acceptable values *
  • "no-cache" ; Section 14.9.1
  • *
  • "no-store" ; Section 14.9.2
  • *
  • "max-age" "=" delta-seconds ; Section 14.9.3, 14.9.4
  • *
  • "max-stale" [ "=" delta-seconds ] ; Section 14.9.3
  • *
  • "min-fresh" "=" delta-seconds ; Section 14.9.3
  • *
  • "no-transform" ; Section 14.9.5
  • *
  • "only-if-cached" ; Section 14.9.4
  • *
  • cache-extension ; Section 14.9.6 Not implemented use Advances
  • * * @param context * wiki context * @param value * The value passed to this header */ public void processCacheControl(WikiContext context, String value) { if (value != null && Pattern .compile( "^(no-cache|no-store|max-age *= *\\d*|max-stale(| *= *\\d*)|min-fresh *= *\\d*|no-transform|only-if-cached)$", Pattern.CASE_INSENSITIVE).matcher(value).find()) { TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, HEADER_CACHE_CONTROL + ": " + value); // rfc states that if http/1.1 Cache control = no-cache // server should alwo set http/1.0 Pragma no-cache if (value.equalsIgnoreCase("no-cache")) { processPragma(context, value); } } else { log.info(value + " is not valid for " + HEADER_CACHE_CONTROL); } } /** *
  • Date ; Section 14.18
  • * * @param context * wiki context * @param value * The value passed to this header */ public void processDate(WikiContext context, String value) { if (context.getVariable(TemplateManager.RESOURCE_HTTPHEADER + HEADER_DATE) == null) { log.info("Date header already processed for this page"); return; } // Format dates according to RFC 822, updated by RFC 1123 DateFormat httpDateFormat = new SimpleDateFormat( "EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z"); // Start with the page date Date headerDate = context.getRealPage().getLastModified(); if (value != null) { try { // If we have a header date us it instead headerDate = httpDateFormat.parse(value); } catch (ParseException e) { log.info("Unable to parse date from " + value); log.debug("Using context.getRealPage().getLastModified()"); headerDate = context.getRealPage().getLastModified(); } } // Header timezone should be GMT httpDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, HEADER_DATE + ": " + httpDateFormat.format(headerDate)); context.setVariable(TemplateManager.RESOURCE_HTTPHEADER + HEADER_DATE, "x"); } /** *
  • Pragma ; Section 14.32
  • * * @param context * wiki context * @param value * The value passed to this header */ public void processPragma(WikiContext context, String value) { if (value != null && Pattern.compile( "^(no-cache|\\w+ *= *\\w+|\\w+ *= *\".*\")$", Pattern.CASE_INSENSITIVE).matcher(value).find()) { TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, HEADER_PRAGMA + ": " + value); } else { log.info(value + " is not valid for " + HEADER_PRAGMA); } } /** *
  • Age ; Section 14.6
  • * * @param context * This is the Wiki Context * @param value * The value for the header */ public void processAge(WikiContext context, String value) { if (context.getVariable(TemplateManager.RESOURCE_HTTPHEADER + HEADER_AGE) == null) { log.info("Age header already processed for this page"); return; } // Format dates according to RFC 822, updated by RFC 1123 DateFormat httpDateFormat = new SimpleDateFormat( "EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z"); // Start with the time in seconds from the lastmodified date to now long headerDate = new Date().getTime() - context.getRealPage().getLastModified().getTime(); if (value != null) { try { // If we have a header date us it instead headerDate = httpDateFormat.parse(value).getTime(); } catch (ParseException e) { log.info("Unable to parse date from " + value); log .debug("Using Date().getTime() - context.getRealPage().getLastModified().getTime()"); headerDate = new Date().getTime() - context.getRealPage().getLastModified().getTime(); } } // Header timezone should be GMT httpDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, HEADER_AGE + ": " + headerDate); context.setVariable(TemplateManager.RESOURCE_HTTPHEADER + HEADER_DATE, "x"); } /** *
  • ETag ; Section 14.19
  • * If value is null, then ETag will equal Wiki.Name Page.Name Page.Version * * @param context * This is the Wiki Context * @param value * The value for the header */ public void processETag(WikiContext context, String value) { if (context.getVariable(TemplateManager.RESOURCE_HTTPHEADER + HEADER_ETAG) == null) { log.info("ETag header already processed for this page"); return; } TemplateManager.addResourceRequest(context, TemplateManager.RESOURCE_HTTPHEADER, HEADER_ETAG + ": " + value != null ? value : context .getRealPage().getWiki() + "." + context.getRealPage().getName() + "." + context.getRealPage().getVersion()); context.setVariable(TemplateManager.RESOURCE_HTTPHEADER + HEADER_ETAG, "x"); } }