package uk.ac.omii.springweb.jspwiki; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.log4j.Logger; import com.ecyrd.jspwiki.WikiContext; import com.ecyrd.jspwiki.WikiEngine; import com.ecyrd.jspwiki.auth.permissions.PagePermission; import com.ecyrd.jspwiki.filters.BasicPageFilter; import com.ecyrd.jspwiki.filters.FilterException; public class ACLFilter extends BasicPageFilter { private static final Logger logger = Logger.getLogger(ACLFilter.class); /* * Reluctant quantifier: choose the shortest match. * (?s): DOTALL mode: "." includes line terminator as well. */ private static final String PREFORMATTED_PATTERN = "(?s)\\{\\{\\{.*?\\}\\}\\}"; private static final String ACTION_PATTERN = "(" + PagePermission.COMMENT_ACTION + "|" + PagePermission.DELETE_ACTION + "|" + PagePermission.EDIT_ACTION + "|" + PagePermission.MODIFY_ACTION + "|" + PagePermission.RENAME_ACTION + "|" + PagePermission.UPLOAD_ACTION + "|" + PagePermission.VIEW_ACTION + ")"; // private static final String ACL_PATTERN = "\\[\\{\\s*ALLOW\\s+" + "[a-zA-Z]+" + "\\s*(.*?)\\s*\\}\\]"; private static final String ACL_PATTERN = "\\[\\{\\s*ALLOW\\s+" + ACTION_PATTERN + "\\s*(.*?)\\s*\\}\\]"; private Pattern prePattern = Pattern.compile(PREFORMATTED_PATTERN); private Pattern aclPattern = Pattern.compile(ACL_PATTERN); /** * Is called whenever the a new PageFilter is instantiated and * reset. */ @Override public void initialize(WikiEngine engine, Properties properties) throws FilterException { super.initialize(engine, properties); } /** * This method is called before the page has been saved to the PageProvider. */ @Override public String preSave(WikiContext wikiContext, String content) throws FilterException { /* * if the current user has administrative permissions, i.e. the omnipotent AllPermission, * in our case, WIKIADMIN, save it directly. */ if (wikiContext.hasAdminPermissions()) return content; /* * Filter out all ACLs. Non-admin users have no right to define ACLs. */ StringBuffer newContent = filterACLs(content); /* * Retrieve the official ACLs from the current version. */ String currentContent = wikiContext.getEngine().getPureText(wikiContext.getPage()); StringBuffer officialACLs = retrieveOfficialACLs(currentContent); return newContent.append(officialACLs).toString(); } private static final int ACL_PROCESS_MODE_FILTER = 0; private static final int ACL_PROCESS_MODE_COLLECT = 1; private StringBuffer filterACLs(String content) { return processACLs(content, ACL_PROCESS_MODE_FILTER); } private StringBuffer retrieveOfficialACLs(String currentContent) { return processACLs(currentContent, ACL_PROCESS_MODE_COLLECT); } private StringBuffer processACLs(String content, int mode) { StringBuffer rv; if (mode == ACL_PROCESS_MODE_FILTER) rv = new StringBuffer(content.length()); else // mode == ACL_PROCESS_MODE_COLLECT rv = new StringBuffer(); StringBuffer sb = new StringBuffer(content); /* * First, find all preformatted ranges, within which ACLs has no meaning. */ Matcher m = prePattern.matcher(sb); ArrayList prelist = new ArrayList(); while (m.find()) { Range range = new Range(m.start(), m.end()); prelist.add(range); } /* * Second, find all ACLs, considering preformatted ranges. */ m = aclPattern.matcher(sb); while (m.find()) { Range range = new Range(m.start(), m.end()); if (Collections.binarySearch(prelist, range, new Comparator() { public int compare(Range r1, Range r2) { if (r1.start > r2.end) return 1; else if (r1.end < r2.start) return -1; else return 0; } }) < 0) { if (mode == ACL_PROCESS_MODE_FILTER) m.appendReplacement(rv, ""); else // mode == ACL_PROCESS_MODE_COLLECT rv.append(m.group()); } } if (mode == ACL_PROCESS_MODE_FILTER) m.appendTail(rv); return rv; } private static class Range { int start; int end; Range(int s, int e) { this.start = s; this.end = e; } } }