/**
*
* 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");
}
}