001package 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. 002 003import java.io.*; 004import java.net.*; 005import javax.servlet.http.*; 006import org.apache.wicket.request.*; 007import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler; 008import org.apache.wicket.request.http.handler.ErrorCodeRequestHandler; 009import org.apache.wicket.util.resource.*; 010import votorola.a.*; 011import votorola.g.lang.*; 012 013 014/** A request mapper for serving an external configuration directory from Wicket. 015 * 016 * <p class='indent'>~/{@linkplain VoteServer#votorolaDirectory() 017 * votorola}/web/publicConfig</p> 018 * 019 * <p>This is for static pages such as Crossforum Theatre that seek their configuration 020 * relative to the page itself. We do not want to actually locate the configuration with 021 * the page code, so instead we mount it there using this mapper. It is mounted to the 022 * following location, where <var>CONTEXT/w</var> is the path to the Wicket filter, as 023 * defined in <a 024 * href='../../../../../../a/web/context/WEB-INF/web.xml'>WEB-INF/web.xml</a>:</p> 025 * 026 * <p class='indent'><var>CONTEXT/w</var>/publicConfig</p> 027 */ 028final @ThreadSafe class PublicConfigRequestMapper implements IRequestMapper 029{ 030 031 PublicConfigRequestMapper( final VoteServer voteServer ) 032 { 033 try 034 { 035 directoryURI = new URI( "file", voteServer.votorolaDirectory().getAbsolutePath() 036 + "/web/" + REQUEST_PATH_PREFIX, /*fragment*/null ); // all encoded by constructor 037 } 038 catch( URISyntaxException x ) { throw new RuntimeException( x ); } 039 } 040 041 042 // - I - R e q u e s t - M a p p e r ---------------------------------------------- 043 044 045 public int getCompatibilityScore( final Request request ) 046 { 047 return score( request.getUrl().getPath() ); 048 } 049 050 051 052 public Url mapHandler( final IRequestHandler rH ) 053 { 054 final Url url; 055 if( rH instanceof RequestHandler ) url = ((RequestHandler)rH).requestUrl; 056 else url = null; 057 return url; 058 } 059 060 061 062 public IRequestHandler mapRequest( final Request request ) 063 { 064 final Url requestUrl = request.getUrl(); 065 String requestPath = requestUrl.getPath(); 066 final int score = score( requestPath ); 067 final IRequestHandler handler; 068 if( score < 0 ) handler = null; 069 else 070 { 071 requestPath = requestPath.substring( REQUEST_PATH_PREFIX.length() ); 072 try 073 { 074 final URI requestURI = new URI( /*scheme*/null, requestPath, /*fragment*/null ); // encoded by constructor 075 final URI actualURI = directoryURI.resolve( requestURI ); 076 if( actualURI.getPath().startsWith( directoryURI.getPath() )) // probably guaranteed 077 { 078 handler = new RequestHandler( requestUrl, actualURI ); 079 } 080 else handler = new ErrorCodeRequestHandler( HttpServletResponse.SC_FORBIDDEN, 081 "request path '" + requestPath + "'is outside of publicConfig directory" ); 082 // Might instead guard with file.getCanonicalPath(), but it resolves 083 // symbolic links to their targets. Targets themselves would then have 084 // to be located in the directory, which seems too restrictive. 085 } 086 catch( URISyntaxException x ) { throw new RuntimeException( x ); } 087 } 088 return handler; 089 } 090 091 092 093//// P r i v a t e /////////////////////////////////////////////////////////////////////// 094 095 096 private final URI directoryURI; 097 098 099 100 private static final String REQUEST_PATH_PREFIX = "publicConfig/"; // relative to Wicket filter 101 102 103 104 /** @param requestPath the request path relative to the Wicket filter 105 */ 106 private static int score( final String requestPath ) 107 { 108 return requestPath.startsWith(REQUEST_PATH_PREFIX)? Integer.MAX_VALUE: Integer.MIN_VALUE; 109 // No matter how small the failure score, 1.5.3 calls mapRequest regardless. 110 // Maybe it normalizes negative scores to zero, and zero is the success score 111 // for the default MountedMapper? 112 } 113 114 115 116 // ==================================================================================== 117 118 119 private static final class RequestHandler extends ResourceStreamRequestHandler 120 { 121 122 private RequestHandler( Url _requestUrl, final URI actualURI ) 123 { 124 super( new FileResourceStream( new File( actualURI ))); 125 requestUrl = _requestUrl; 126 } 127 128 129 private final Url requestUrl; 130 131 } 132 133 134}