package org.greenstone.gsdl3.core;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import org.apache.log4j.Logger;
import org.greenstone.gsdl3.util.HttpHeaderUtil;
import org.greenstone.gsdl3.util.GSParams;
import org.greenstone.gsdl3.util.GSPath;
import org.greenstone.gsdl3.util.GSXML;
import org.greenstone.gsdl3.util.OID;
import org.greenstone.gsdl3.util.RecaptchaUtil;
import org.greenstone.gsdl3.util.UserContext;
import org.greenstone.gsdl3.util.XMLConverter;
import org.greenstone.gsdl3.service.Authentication;
import org.greenstone.gsdl3.service.AbstractTextSearch;
import org.greenstone.gsdl3.service.AbstractGS2FieldSearch;
import org.greenstone.gsdl3.service.AbstractBrowse;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class URLFilter implements Filter
{
  private FilterConfig _filterConfig = null;
  private static Logger logger = Logger.getLogger(org.greenstone.gsdl3.core.URLFilter.class.getName());

  //Restricted URLs
  protected static final String SITECONFIG_URL = "sites/[^/]+/siteConfig.xml";
  protected static final String USERS_DB_URL = "etc/usersDB/.*";
  protected static final ArrayList<String> _restrictedURLs;

  static
  {
    ArrayList<String> restrictedURLs = new ArrayList<String>();
    restrictedURLs.add(SITECONFIG_URL);
    restrictedURLs.add(USERS_DB_URL);
    _restrictedURLs = restrictedURLs;
  }
  
  //Constants
  protected static final String DOCUMENT_PATH = "document";
  protected static final String COLLECTION_PATH = "collection";
  protected static final String GROUP_PATH = "group";
  protected static final String PAGE_PATH = "page";
  protected static final String ADMIN_PATH = "admin";
  protected static final String SYSTEM_PATH = "system";
  protected static final String BROWSE_PATH = "browse";
  protected static final String SEARCH_PATH = "search";
  protected static final String ASSOC_PATH = "assoc";
  protected static final ArrayList<String> _keywords;

  static
  {
    ArrayList<String> keywords = new ArrayList<String>();
    keywords.add(PAGE_PATH);
    keywords.add(BROWSE_PATH);
    keywords.add(SEARCH_PATH);
    keywords.add(DOCUMENT_PATH);
    _keywords = keywords;
  }
  
  protected static final String METADATA_RETRIEVAL_SERVICE = "DocumentMetadataRetrieve";
  protected static final String ASSOCIATED_FILE_PATH = "/index/assoc/";
  protected static final String COLLECTION_FILE_PATH = "/collect/";
  protected static final String INTERFACE_PATH = "/interfaces/";
  protected static final String SITES_PATH = "/sites/";
  
  protected static final String SYSTEM_SUBACTION_CONFIGURE = "configure";
  protected static final String SYSTEM_SUBACTION_RECONFIGURE = "reconfigure";
  protected static final String SYSTEM_SUBACTION_ACTIVATE = "activate";
  protected static final String SYSTEM_SUBACTION_DEACTIVATE = "deactivate";

  // if we are showing terms and conditions to user, this remembers who has
  // accepted already
  protected Hashtable<String, UserTimer> verifiedUserMap = null;
    // timeouts are in millisecs
    // this is for if we have verify=once set in collectionConfig - the user will stay
    // verified for 24 hours
  protected static final int verifiedUserTimeout = 24 * 60 * 60 * 1000;
    // this is a per document timeout - Chrome may make several requests to fetch a fastview pdf,
    // plus another one if the user clicks download. Need to keep a record for a verified document
    // so it can be fuly viewed and downloaded - get a network error if end up back at verification page.
    // the user stays verified for the document for 2 hours.
    protected static final int tempUserTimeout = 2 * 60 * 60 * 1000;
  
  public void init(FilterConfig filterConfig) throws ServletException
  {
    this._filterConfig = filterConfig;
    this.verifiedUserMap = new Hashtable<String, UserTimer>();
  }

  public void destroy()
  {
    this._filterConfig = null;
  }

  @SuppressWarnings("deprecation")
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
  {
    if (!(request instanceof HttpServletRequest)) {
      // Can this ever happen?
      logger.error("The request was not an HttpServletRequest");
      return;
    }
	  
    HttpServletRequest hRequest = ((HttpServletRequest) request);
    HttpSession hSession = hRequest.getSession();
    ServletContext sContext = hSession.getServletContext();

    String external_context = HttpHeaderUtil.getExternalContext(hRequest); // the /greenstone3 part of external url
    if (external_context.equals("/")) {
	external_context = "";
    }
    // logger.error("external context = "+external_context);
    //logger.error("internal context = "+sContext.getContextPath());

    /* Uncomment the following if using web browser-based WorkerThread */
    /* An example extension that needs these headers set the tartan-ify part of 'web-audio' */
    // TODO: allow such headers to be optionally set in build.properties file
    
    //HttpServletResponse hResponse = (HttpServletResponse)response;
    //hResponse.setHeader("Cross-Origin-Opener-Policy", "same-origin");
    //hResponse.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
	    
    GSHttpServletRequestWrapper gRequest = new GSHttpServletRequestWrapper(hRequest);
	  
    // this is the part before the ?
    // String url = hRequest.getRequestURI().toString();
    String url = HttpHeaderUtil.externalRequestedURI(hRequest);

    if (isURLRestricted(url)) {
      
      // TODO - should we make this a proper HTML page?
      response.getWriter().println("ERROR: Access to this page is forbidden.");
      return;
    }


    // Run security checks on files requested from a collection's index/assoc folder
    if (url.contains(ASSOCIATED_FILE_PATH)) {
      // we need to do security checking here in case the documents are private
      // or if there is a terms and conditions page to be shown.
      // Also, assoc files urls now contain the library name in them.
      // e.g. greenstone3/library/sites/localsite/collect/collname/index/assoc/Hashxxx/doc.pdf
      // so they are not a true link to a file. We remove the
      // 'library' then forward the request to the new url.
      securityCheckAssocFiles(url, hRequest, response, null);
      return;
    }

    //
    if (url.contains(SITES_PATH)) {
      // there are some site/collection images that are not associated files.
      // these dont need to be security checked, but we need to remove the library name from the url if its there
      
      String regex = external_context+"/.+"+SITES_PATH+".*";
      if (url.matches(regex)) {
	// a forward doesn't want the context path
	String new_url = url.substring(url.indexOf(SITES_PATH));
	request.getRequestDispatcher(new_url).forward(request, response);
	return;
      }
      // else if it doesn't match, ie the url was /greenstone3/sites/...
      // we don't do anything and just let it continue
    }
    
      

    // if we are asking for an interface file, and it doesn't exist, then
    // try loading up the interfaceConfig file to find out what the base
    // interface was, then try looking in that interface for the file.
    if (url.contains(INTERFACE_PATH))
      {
	  String fileURL = url;
	  if (!external_context.equals("")) {
	      fileURL = fileURL.replaceFirst(external_context, "");
	  }
	File requestedFile = new File(sContext.getRealPath(fileURL));

	if (!requestedFile.exists()) {
	  String baseURL = getFileInBaseInterface(fileURL, sContext);
	  if (baseURL != null) {
	    File baseInterfaceFile = new File(sContext.getRealPath(baseURL));
	    if (baseInterfaceFile.exists()) {
	      request.getRequestDispatcher(baseURL).forward(request, response);
	      // ServletOutputStream out = response.getOutputStream();
	      // out.write(FileUtils.readFileToByteArray(baseInterfaceFile));
	      // out.flush();
	      // out.close();
	      return;
	    }
	  }
	}
      }
    else
      {
	//If we have a jsessionid on the end of our URL we want to ignore it
	int index;
	if ((index = url.indexOf(";jsessionid")) != -1)
	  {
	    url = url.substring(0, index);
	  }

        // OK, here we split into segments and process bit by bit.
	String[] segments = url.split("/");
	for (int i = 0; i < segments.length; i++)
	  {
	    String[] additionalParameters = null;
	    String[] defaultParamValues = null;
	    //COLLECTION
	    if (segments[i].equals(COLLECTION_PATH) && (i + 1) < segments.length) {
	      int j=i+1;
	      while(j+1 < segments.length && !_keywords.contains(segments[j+1])) {
		j++;
	      }
					  
	      if (j>i+1) {
		// we had a group part
		String [] groups = Arrays.copyOfRange(segments, i+1, j);
		String group = StringUtils.join(groups, "/");
		gRequest.setParameter(GSParams.GROUP, group);
	      }
	      gRequest.setParameter(GSParams.COLLECTION, segments[j]);
	    }
	    // GROUP
	    else if(segments[i].equals(GROUP_PATH) && (i + 1) < segments.length)
	      {
		// assume for now, no other path parts for group links
		int j= segments.length - 1;
		String group;
		if (j==i+1) {
		  group = segments[j];
		} else {
		  String [] groups = Arrays.copyOfRange(segments, i+1, j+1);
		  group = StringUtils.join(groups, "/");
		}
		gRequest.setParameter(GSParams.GROUP, group);
		gRequest.setParameter(GSParams.ACTION, GSParams.PAGE_ACTION);
		gRequest.setParameter(GSParams.SUBACTION, "home");
					    
	      }
	    //DOCUMENT
	    else if (segments[i].equals(DOCUMENT_PATH) && (i + 1) < segments.length)
	      {
		gRequest.setParameter(GSParams.DOCUMENT, segments[i + 1]);
		additionalParameters = new String[] { GSParams.ACTION };
		defaultParamValues = new String[] { GSParams.DOCUMENT_ACTION};
		if ((i+2) < segments.length && segments[i+2].equals("print")) {
		  gRequest.setParameter(GSParams.SUBACTION, "print");
		  gRequest.setParameter("ed", "1");
						  
		} else if ((i+3) < segments.length && segments[i+2].equals("assoc")) {
		    url = generateFileURL(sContext, url, external_context, gRequest.getParameter(GSParams.COLLECTION), gRequest.getParameter(GSParams.DOCUMENT), segments[i+3]);
                                    
                  securityCheckAssocFiles(url, hRequest, response, gRequest.getParameter(GSParams.DOCUMENT));
                  return;
                }
                
					
	      }
	    //PAGE
	    else if (segments[i].equals(PAGE_PATH) && (i + 1) < segments.length)
	      {
		gRequest.setParameter(GSParams.SUBACTION, segments[i + 1]);

		additionalParameters = new String[] { GSParams.ACTION };
		defaultParamValues = new String[] { GSParams.PAGE_ACTION };
	      }
	    //SYSTEM
	    else if (segments[i].equals(SYSTEM_PATH) && (i + 1) < segments.length)
	      {
		String sa = segments[i + 1];
		if (sa.equals(SYSTEM_SUBACTION_CONFIGURE) || sa.equals(SYSTEM_SUBACTION_RECONFIGURE))
		  {
		    sa = "c";
		  }
		else if (sa.equals(SYSTEM_SUBACTION_ACTIVATE))
		  {
		    sa = "a";
		  }
		else if (sa.equals(SYSTEM_SUBACTION_DEACTIVATE))
		  {
		    sa = "d";
		  }

		if (sa.equals("c") && (i + 2) < segments.length)
		  {
		    gRequest.setParameter(GSParams.SYSTEM_CLUSTER, segments[i + 2]);
		  }

		if (sa.equals("a") && (i + 2) < segments.length)
		  {
		    gRequest.setParameter(GSParams.SYSTEM_MODULE_TYPE, "collection");
		    gRequest.setParameter(GSParams.SYSTEM_MODULE_NAME, segments[i + 2]);
		  }

		if (sa.equals("d") && (i + 2) < segments.length)
		  {
		    gRequest.setParameter(GSParams.SYSTEM_CLUSTER, segments[i + 2]);
		  }

		gRequest.setParameter(GSParams.SUBACTION, sa);

		additionalParameters = new String[] { GSParams.ACTION };
		defaultParamValues = new String[] { GSParams.SYSTEM_ACTION };
	      }
	    //ADMIN
	    else if (segments[i].equals(ADMIN_PATH) && (i + 1) < segments.length)
	      {
		String pageName = segments[i + 1];

		gRequest.setParameter("s1."+Authentication.AUTHPAGE_PARAM, pageName);

		additionalParameters = new String[] { GSParams.ACTION, GSParams.REQUEST_TYPE, GSParams.SUBACTION, GSParams.SERVICE };
		defaultParamValues = new String[] { GSParams.GENERAL_ACTION, GSParams.REQUEST_TYPE_REQUEST, "authen", Authentication.AUTHENTICATION_SERVICE };
	      }
	    //BROWSE
	    else if (segments[i].equals(BROWSE_PATH) && (i + 1) < segments.length)
	      {
		String cl = "";
		for (int j = 1; (i + j) < segments.length; j++)
		  {
		    String currentSegment = segments[i + j].replace("CL", "").replace("cl", "");
		    if (currentSegment.contains("."))
		      {
			String[] subsegments = currentSegment.split("\\.");
			for (String subsegment : subsegments)
			  {
			    subsegment = subsegment.replace("CL", "").replace("cl", "");

			    if (cl.length() > 0)
			      {
				cl += ".";
			      }

			    if (subsegment.length() > 0)
			      {
				cl += subsegment;
			      }
			  }
			continue;
		      }
		    if (!currentSegment.matches("^(CL|cl)?\\d+$"))
		      {
			continue;
		      }

		    if (cl.length() > 0)
		      {
			cl += ".";
		      }

		    cl += currentSegment;
		  }

		gRequest.setParameter("cl", "CL" + cl);

		additionalParameters = new String[] { GSParams.ACTION, GSParams.REQUEST_TYPE, GSParams.SERVICE };
		defaultParamValues = new String[] { GSParams.BROWSE_ACTION, GSParams.REQUEST_TYPE_REQUEST, AbstractBrowse.CLASSIFIER_SERVICE };
	      }
	    //QUERY
	    else if (segments[i].equals(SEARCH_PATH))
	      {
		String serviceName = "";
		if ((i + 1) < segments.length)
		  {
		    serviceName = segments[i + 1];
		    gRequest.setParameter("s", serviceName);

		    additionalParameters = new String[] { GSParams.ACTION, GSParams.SUBACTION, GSParams.REQUEST_TYPE };
		    defaultParamValues = new String[] { GSParams.QUERY_ACTION, "", GSParams.REQUEST_TYPE_DESCRIBE };
		  }
		if ((i + 2) < segments.length)
		  {
		    if (serviceName.equals(AbstractTextSearch.TEXT_QUERY_SERVICE) || serviceName.equals(AbstractGS2FieldSearch.RAW_QUERY_SERVICE))
		      {

			gRequest.setParameter("s1."+AbstractTextSearch.QUERY_PARAM, segments[i + 2]);
		      }
		    else if (serviceName.equals(AbstractGS2FieldSearch.FIELD_QUERY_SERVICE) || serviceName.equals(AbstractGS2FieldSearch.ADVANCED_FIELD_QUERY_SERVICE))
		      {
			gRequest.setParameter("s1."+AbstractGS2FieldSearch.FIELD_QUERY_PARAM, segments[i + 2]);
		      }
		    
		  }
	      }
	    if (additionalParameters != null)
	      {
		for (int j = 0; j < additionalParameters.length; j++)
		  {
		    if (gRequest.getParameter(additionalParameters[j]) == null)
		      {
			gRequest.setParameter(additionalParameters[j], defaultParamValues[j]);
		      }
		  }
	      }
	  }
      }

    chain.doFilter(gRequest, response);
  }

  private boolean isURLRestricted(String url)
  {
    for (String restrictedURL : _restrictedURLs)
      {
	if (url.matches(".*" + restrictedURL + ".*"))
	  {
	    return true;
	  }
      }

    return false;
  }

  private String getFileInBaseInterface(String fileURL, ServletContext sContext) {
    int interfaceNameStart = fileURL.indexOf(INTERFACE_PATH) + INTERFACE_PATH.length();
    int interfaceNameEnd = fileURL.indexOf("/", interfaceNameStart);
    String interfaceName = fileURL.substring(interfaceNameStart, interfaceNameEnd);
    String interfacesDir = fileURL.substring(0, interfaceNameStart);
    File interfaceConfigFile = new File(sContext.getRealPath(interfacesDir + interfaceName + "/interfaceConfig.xml"));
    
    if (interfaceConfigFile.exists()) {
      
      Document interfaceConfigDoc = XMLConverter.getDOM(interfaceConfigFile);
      
      String baseInterface = interfaceConfigDoc.getDocumentElement().getAttribute("baseInterface");
      if (baseInterface.length() > 0)
	{
	  return fileURL.replace("/" + interfaceName + "/", "/" + baseInterface + "/");
	}
    }
      return null;
  }

    private String generateFileURL(ServletContext sContext, String url, String external_context, String collection, String document, String filename) {

    String library_name = url.replaceFirst(external_context+"/", "");
    library_name = library_name.substring(0, library_name.indexOf("/"));

    MessageRouter gsRouter = (MessageRouter) sContext.getAttribute(library_name+"Router");
    				
    if (gsRouter == null) {  
	logger.error("Looked for "+library_name+"Router, and it is null, stopping filter");
	return null;
    }

    String site_name = gsRouter.getSiteName();
    String assocfilepath = queryMRforMetadata(gsRouter, collection, OID.getTop(document), "assocfilepath");

    if (assocfilepath == null) {
      return null;
    }
    return external_context +"/"+ library_name+"/"+
      "sites/"+ site_name+"/"+
      "collect/"+collection+"/"+
      "index/assoc/"+assocfilepath+"/"+filename;
    
  }

  private void securityCheckAssocFiles(String url, HttpServletRequest request, ServletResponse response, String document) throws IOException, ServletException {

	HttpSession session = request.getSession();
	String session_id = session.getId();
	ServletContext sContext = session.getServletContext();

	String external_context = HttpHeaderUtil.getExternalContext((HttpServletRequest)request);
	if (external_context.equals("/")) {
	    // if external context = "/" this means we have urls like myserver.org/library, with no context
	    external_context = "";
	}
    	// now we need to get library name from the path, which is like
	// /greenstone3/library/sites/localsite/collect/collname/index/assoc/...
	String library_name = url;
	if (!external_context.equals("")) {
	    library_name = library_name.replaceFirst(external_context, "");
	}

	// remove initial /, and everything after /sites
	library_name = library_name.substring(1, library_name.indexOf(SITES_PATH));
	if (library_name.equals("")) {
	  response.getWriter().println("ERROR: Assoc file paths must now contain the library name");
	  return;
	}

	MessageRouter gsRouter = (MessageRouter) sContext.getAttribute(library_name+"Router");
				
	if (gsRouter == null) {  
	  logger.error("Looked for "+library_name+"Router, and it is null, stopping filter");
	  return;
	}
	// Sometimes we have a // before the filename - that mucks up the following code, so lets remove them
	url = url.replaceAll("//","/");
	
	String dir = null;
	int dirStart = url.indexOf(ASSOCIATED_FILE_PATH) + ASSOCIATED_FILE_PATH.length();
	int dirEnd = -1;
	if (dirStart < url.length() && url.indexOf("/", dirStart) != -1)
	  {
	    dirEnd = url.lastIndexOf("/");
	  }
	if (dirEnd != -1)
	  {
	    dir = url.substring(dirStart, dirEnd);
	  }
	if (dir == null)
	  {
	    return;
	  }
	
	String collection = null;
	int colStart = url.indexOf(COLLECTION_FILE_PATH) + COLLECTION_FILE_PATH.length();
	int colEnd = -1;
	if (colStart < url.length() && url.indexOf("/", colStart) != -1)
	  {
	    colEnd = url.indexOf("/", colStart);
	  }
	if (colEnd != -1)
	  {
	    collection = url.substring(colStart, colEnd);
	  }
	if (collection == null)
	  {
	    return;
	  }
	
	String file_name = url.substring(url.lastIndexOf("/")+1);
	// if there is ;jsessionid=xxx in the request, it is still here at this stage!!!
	int semicol = file_name.indexOf(';');
	if(semicol != -1) {
	  file_name = file_name.substring(0, semicol);
	}

        if (document == null) {
	// Query the MR with a request for the contains metadata for node "dir" - where dir is the assocfilepath
	// In the jdbm db, have entries like
	// [HASH1552e]
	// <contains>HASH1552e3sdlkjf7sdfsdfk
	// mapping assocfilepath to doc id
          document = queryMRforDOCID(gsRouter, collection, dir);
          if (document == null) {
            response.getWriter().println("ERROR: Couldn't find the document associated with assocfilepath: "+dir);
            return;
          }
        }
	
	//Query the MR for the security info for this document
	// - can we show the document?
	// - Or do we need to be logged in?
	// - Or do we need to throw up the verify page?
	
	// While we are doing this, query the document for its srclinkFile metadata - then we can determine if the
	// file we are being asked for is the main doc (eg pdf) or just a supporting image on the page

	//Get the security info for this collection
	Document gsDoc = XMLConverter.newDOM();
	Element securityMessage = gsDoc.createElement(GSXML.MESSAGE_ELEM);
	Element securityRequest = GSXML.createBasicRequest(gsDoc, GSXML.REQUEST_TYPE_SECURITY, collection, new UserContext());
					
	securityMessage.appendChild(securityRequest);
	securityRequest.setAttribute(GSXML.NODE_OID, document);
					  
	// get the srclinkFile for the document
	Element metadata_request = GSXML.createBasicRequest(gsDoc, GSXML.REQUEST_TYPE_PROCESS, GSPath.appendLink(collection, "DocumentMetadataRetrieve"), new UserContext());
	Element param_list = gsDoc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
	GSXML.addParameterToList(param_list, "metadata", "srclinkFile");
	metadata_request.appendChild(param_list);
	Element doc_list = gsDoc.createElement(GSXML.DOC_NODE_ELEM + GSXML.LIST_MODIFIER);
	metadata_request.appendChild(doc_list);
	Element d = gsDoc.createElement(GSXML.DOC_NODE_ELEM);
	d.setAttribute(GSXML.NODE_ID_ATT, document);
	doc_list.appendChild(d);
	securityMessage.appendChild(metadata_request);
	      
					
	Element mr_response = (Element)gsRouter.process(securityMessage);
	//logger.debug("security response = "+XMLConverter.getPrettyString(mr_response));

	boolean verifiable_file = true;
	// TODO check for errors

	Element meta_response = (Element) GSXML.getNamedElement(mr_response, GSXML.RESPONSE_ELEM, GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
	Element metadata_list = (Element)meta_response.getElementsByTagName(GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER).item(0);
	String srcdoc = GSXML.getMetadataValue(metadata_list, "srclinkFile");
	//logger.debug("srcdoc="+srcdoc+", filename="+file_name+", %20 decoded filename="+file_name.replaceAll("\\%20|\\+", " "));
	// If file_name is the main file for the document, then it will == srcdoc. Both of these are URL encoded, with the exception of spaces. Spaces will be encoded in file_name, but are not encoded in srcdoc. So need to decode those and check again.
	// srcdoc.equals(java.net.URLDecoder.decode(file_name, "UTF-8")) - this didn't work as both are URLEncoded except for spaces 
	if (!srcdoc.equals(file_name) && !srcdoc.equals(file_name.replaceAll("\\%20|\\+", " "))){
	  // the specified file is just a supporting file, not the main file.
	  // eg an image in an html doc.
	  verifiable_file = false;
	}

	Element securityResponse = (Element) GSXML.getNamedElement(mr_response, GSXML.RESPONSE_ELEM, GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_SECURITY);
        if (!securityResponse.getAttribute("noSecurity").equals("true")) {
          // there is a security concern for this doc
        
        
	ArrayList<String> groups = GSXML.getGroupsFromSecurityResponse(securityResponse);
				
	if (!groups.contains(""))
	  {
	    boolean found = false;
	    for (String group : groups)
	      {
		if (((HttpServletRequest) request).isUserInRole(group))
		  {
		    found = true;
		    break;
		  }
	      }

	    if (!found)
	      {
		// return an error page to the browser
		//String new_url = context.getContextPath()+"/"+ library_name+"?a=p&sa=error&c="+collection+"&ec=wrong_group";
		  String new_url = external_context+"/"+ library_name+"?a=p&sa=error&c="+collection+"&ec=wrong_group";
		((HttpServletResponse)response).sendRedirect(new_url);
		return;
	      }
	  }
	}
	// if got here have no groups that we need to belong to
	// do we have human verify thing?
	if (verifiable_file) {
	    String verify = securityResponse.getAttribute(GSXML.VERIFY_ATT);
	    String use_recaptcha = securityResponse.getAttribute(GSXML.USE_RECAPTCHA_ATT); 
	  if (!verify.equals("")) {
	      // we are asking for the main document, and we have been asked to verify the user 
	      // have we done the test previously?
	      String verify_map_key = session_id + ":"+collection;
	      String verify_map_doc_key = verify_map_key + ":" + file_name; 
	      boolean already_verified = false;
	      String hmvf_response = request.getParameter(GSParams.VERIFIED);
	      if (hmvf_response != null && hmvf_response.equals("0")) {
		  // manually force the t&c (user has added hmvf=0 to url)
		  // whether we have previously verified or not
	      } else if (verifiedUserMap.containsKey(verify_map_key) || verifiedUserMap.containsKey(verify_map_doc_key)) {
		  already_verified = true;
	      }

	  if (!already_verified) {
	      // have we just  done the test?
	      // hmvf param will be set by form if the verify page was submitted
	      if (hmvf_response != null && hmvf_response.equals("1")) {
		  if (use_recaptcha.equals("true")) {
		  String recaptcha_response = request.getParameter(Authentication.RECAPTCHA_RESPONSE_PARAM);
		  int result = RecaptchaUtil.verifyRecaptcha(recaptcha_response);
		  if (result == RecaptchaUtil.NO_ERROR) {
		    already_verified = true;
		  } else {
		    logger.error("something went wrong with recaptcha, error="+result);
		    logger.error(Authentication.getErrorKey(result));
		    // display error page
		    String new_url = external_context+"/"+ library_name+"?a=p&sa=error&c="+collection+"&ec=recap_fail";				
		    ((HttpServletResponse)response).sendRedirect(new_url);
		    return;
		  }
		}
		already_verified = true;
		// set up a timer for this verification - standard 24hour if
		// verify==once, short, doc specific one otherwise (browsers seem to be trying to
		// download prfs twice. Chrome gets stuck if the second time
		// doesn't get verified. Also Chrome sends a second request if the
		// user tries to download the document after viewing it. )
		int delay;
		String this_key;
		if (verify.equals("once")) {
		    delay = verifiedUserTimeout;
		    this_key = verify_map_key;
		} else {
		    delay = tempUserTimeout;
		    this_key = verify_map_doc_key;
		}
		UserTimer timer = new UserTimer(delay, this_key);
		verifiedUserMap.put(this_key, timer);
		timer.start();
		// OLD WAY: For the verify page, we just return back to the browser, as we have called this
		// using ajax.
		//return;
		// no longer submitting using ajax, so do the redirect here
		if (!external_context.equals("")) {
		    url = url.replaceFirst(external_context, "");
		}
		url = url.replaceFirst("/"+library_name, "");
		request.getRequestDispatcher(url).forward(request, response);
		
		return;
	      
	      } // hmvf = 1
	  }
	  
	  if (!already_verified) {
	    // hmvf param is not set - we haven't shown them the form yet
	    // or we have been asked to force the T&C
	    // we need to display the verify page
	      //Lets encode the url parameter as we need it encoded in the page.

	      String new_url = external_context+"/"+ library_name+"?a=p&sa=verify&c="+collection+"&url="+java.net.URLEncoder.encode(url, "UTF-8");

	      ((HttpServletResponse)response).sendRedirect(new_url);
	    return;
	  }
	  } // end verify is set
	}// end if verifiable file 
    
	// if we got here, we have passed all security checks and just want to view the file.
	// However, we need to remove the library_name from the URL. As can't change the
	// existing URL, we need to forward to the new one.
	// (Can't do redirect as it will come back into this code and fail as there won't be library in the url)
	// Remove the context and library name parts.
	// don't know what happens with the rest of the filter chain? Does this bypass that??
	if (!external_context.equals("")) {
	    url = url.replaceFirst(external_context, "");
	}
	url = url.replaceFirst("/"+library_name, "");
	//logger.info("forwarding to "+url);
	request.getRequestDispatcher(url).forward(request, response);

	return;
  }




  private String queryMRforDOCID(MessageRouter gsRouter, String collection, String assocfiledir) {
    return queryMRforMetadata(gsRouter, collection, assocfiledir, "contains");
  }

  // set node_id to be a document_id, and metadata_name to be 'assocfilepath' to
  // get the assocfilepath for a particular doc
  // set node_id to be the assocfilepath, and metadata_name to be 'contains' to
  // get the doc_id for a particular assocfilepath
  private String queryMRforMetadata(MessageRouter gsRouter, String collection, String node_id, String metadata_name) {
    Document gsDoc = XMLConverter.newDOM();

    Element metaMessage = gsDoc.createElement(GSXML.MESSAGE_ELEM);
    Element metaRequest = GSXML.createBasicRequest(gsDoc, GSXML.REQUEST_TYPE_PROCESS, collection + "/" + METADATA_RETRIEVAL_SERVICE, new UserContext());
    metaMessage.appendChild(metaRequest);

    Element paramList = gsDoc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
    metaRequest.appendChild(paramList);
    
    Element param = gsDoc.createElement(GSXML.PARAM_ELEM);    
    param.setAttribute(GSXML.NAME_ATT, "metadata");
    param.setAttribute(GSXML.VALUE_ATT, metadata_name);
    paramList.appendChild(param);

    Element docList = gsDoc.createElement(GSXML.DOC_NODE_ELEM + GSXML.LIST_MODIFIER);
    metaRequest.appendChild(docList);

    Element doc = gsDoc.createElement(GSXML.DOC_NODE_ELEM);
    doc.setAttribute(GSXML.NODE_ID_ATT, node_id);
    docList.appendChild(doc);
    
    Element metaResponse = (Element) gsRouter.process(metaMessage);
    
    NodeList metadataList = metaResponse.getElementsByTagName(GSXML.METADATA_ELEM);
    if (metadataList.getLength() == 0) {
      
      logger.error("Could not find the metadata '"+metadata_name+"' for node '"+node_id+"'");
      return null;
    }
      
    Element metadata = (Element) metadataList.item(0);
    String meta_value = metadata.getTextContent();
    if (meta_value != null && meta_value.equals("")) {
      meta_value = null;
    }
    return meta_value;
      
 
  }

  private class UserTimer extends Timer implements ActionListener
  {
    String id = "";

      /* delay in milliseconds */
    public UserTimer(int delay, String id)
    {
      super(delay, (ActionListener) null);
      addActionListener(this);
      this.id = id;
    }
    
    public void actionPerformed(ActionEvent e)
    {
      verifiedUserMap.remove(id);
      stop();
    }
    
  }
  
  
}
