package votorola.a.web.wic; // Copyright 2012, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import java.io.*; import java.net.*; import javax.servlet.http.*; import org.apache.wicket.request.*; import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; import org.apache.wicket.request.http.handler.ErrorCodeRequestHandler; import org.apache.wicket.util.resource.*; import votorola.a.*; import votorola.g.lang.*; /** A request mapper for serving an external configuration directory from Wicket. * *

~/{@linkplain VoteServer#votorolaDirectory() * votorola}/web/publicConfig

* *

This is for static pages such as Crossforum Theatre that seek their configuration * relative to the page itself. We do not want to actually locate the configuration with * the page code, so instead we mount it there using this mapper. It is mounted to the * following location, where CONTEXT/w is the path to the Wicket filter, as * defined in WEB-INF/web.xml:

* *

CONTEXT/w/publicConfig

*/ final @ThreadSafe class PublicConfigRequestMapper implements IRequestMapper { PublicConfigRequestMapper( final VoteServer voteServer ) { try { directoryURI = new URI( "file", voteServer.votorolaDirectory().getAbsolutePath() + "/web/" + REQUEST_PATH_PREFIX, /*fragment*/null ); // all encoded by constructor } catch( URISyntaxException x ) { throw new RuntimeException( x ); } } // - I - R e q u e s t - M a p p e r ---------------------------------------------- public int getCompatibilityScore( final Request request ) { return score( request.getUrl().getPath() ); } public Url mapHandler( final IRequestHandler rH ) { final Url url; if( rH instanceof RequestHandler ) url = ((RequestHandler)rH).requestUrl; else url = null; return url; } public IRequestHandler mapRequest( final Request request ) { final Url requestUrl = request.getUrl(); String requestPath = requestUrl.getPath(); final int score = score( requestPath ); final IRequestHandler handler; if( score < 0 ) handler = null; else { requestPath = requestPath.substring( REQUEST_PATH_PREFIX.length() ); try { final URI requestURI = new URI( /*scheme*/null, requestPath, /*fragment*/null ); // encoded by constructor final URI actualURI = directoryURI.resolve( requestURI ); if( actualURI.getPath().startsWith( directoryURI.getPath() )) // probably guaranteed { handler = new RequestHandler( requestUrl, actualURI ); } else handler = new ErrorCodeRequestHandler( HttpServletResponse.SC_FORBIDDEN, "request path '" + requestPath + "'is outside of publicConfig directory" ); // Might instead guard with file.getCanonicalPath(), but it resolves // symbolic links to their targets. Targets themselves would then have // to be located in the directory, which seems too restrictive. } catch( URISyntaxException x ) { throw new RuntimeException( x ); } } return handler; } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private final URI directoryURI; private static final String REQUEST_PATH_PREFIX = "publicConfig/"; // relative to Wicket filter /** @param requestPath the request path relative to the Wicket filter */ private static int score( final String requestPath ) { return requestPath.startsWith(REQUEST_PATH_PREFIX)? Integer.MAX_VALUE: Integer.MIN_VALUE; // No matter how small the failure score, 1.5.3 calls mapRequest regardless. // Maybe it normalizes negative scores to zero, and zero is the success score // for the default MountedMapper? } // ==================================================================================== private static final class RequestHandler extends ResourceStreamRequestHandler { private RequestHandler( Url _requestUrl, final URI actualURI ) { super( new FileResourceStream( new File( actualURI ))); requestUrl = _requestUrl; } private final Url requestUrl; } }