001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.ui; 020 021import org.apache.log4j.Logger; 022import org.apache.wiki.InternalWikiException; 023import org.apache.wiki.api.core.Command; 024import org.apache.wiki.api.core.Engine; 025import org.apache.wiki.api.core.Page; 026import org.apache.wiki.api.exceptions.ProviderException; 027import org.apache.wiki.api.providers.WikiProvider; 028import org.apache.wiki.api.spi.Wiki; 029import org.apache.wiki.auth.GroupPrincipal; 030import org.apache.wiki.pages.PageManager; 031import org.apache.wiki.parser.MarkupParser; 032import org.apache.wiki.url.URLConstructor; 033import org.apache.wiki.util.TextUtil; 034 035import javax.servlet.http.HttpServletRequest; 036import java.io.IOException; 037import java.util.HashMap; 038import java.util.Map; 039import java.util.Properties; 040 041 042/** 043 * <p>Default implementation for {@link CommandResolver}</p> 044 * 045 * @since 2.4.22 046 */ 047public final class DefaultCommandResolver implements CommandResolver { 048 049 /** Private map with request contexts as keys, Commands as values */ 050 private static final Map< String, Command > CONTEXTS; 051 052 /** Private map with JSPs as keys, Commands as values */ 053 private static final Map< String, Command > JSPS; 054 055 /* Store the JSP-to-Command and context-to-Command mappings */ 056 static { 057 CONTEXTS = new HashMap<>(); 058 JSPS = new HashMap<>(); 059 final Command[] commands = AllCommands.get(); 060 for( final Command command : commands ) { 061 JSPS.put( command.getJSP(), command ); 062 CONTEXTS.put( command.getRequestContext(), command ); 063 } 064 } 065 066 private static final Logger LOG = Logger.getLogger( DefaultCommandResolver.class ); 067 068 private final Engine m_engine; 069 070 /** If true, we'll also consider english plurals (+s) a match. */ 071 private final boolean m_matchEnglishPlurals; 072 073 /** Stores special page names as keys, and Commands as values. */ 074 private final Map<String, Command> m_specialPages; 075 076 /** 077 * Constructs a CommandResolver for a given Engine. This constructor will extract the special page references for this wiki and 078 * store them in a cache used for resolution. 079 * 080 * @param engine the wiki engine 081 * @param properties the properties used to initialize the wiki 082 */ 083 public DefaultCommandResolver( final Engine engine, final Properties properties ) { 084 m_engine = engine; 085 m_specialPages = new HashMap<>(); 086 087 // Skim through the properties and look for anything with the "special page" prefix. Create maps that allow us look up 088 // the correct Command based on special page name. If a matching command isn't found, create a RedirectCommand. 089 for( final String key : properties.stringPropertyNames() ) { 090 if ( key.startsWith( PROP_SPECIALPAGE ) ) { 091 String specialPage = key.substring( PROP_SPECIALPAGE.length() ); 092 String jsp = properties.getProperty( key ); 093 if ( jsp != null ) { 094 specialPage = specialPage.trim(); 095 jsp = jsp.trim(); 096 Command command = JSPS.get( jsp ); 097 if ( command == null ) { 098 final Command redirect = RedirectCommand.REDIRECT; 099 command = redirect.targetedCommand( jsp ); 100 } 101 m_specialPages.put( specialPage, command ); 102 } 103 } 104 } 105 106 // Do we match plurals? 107 m_matchEnglishPlurals = TextUtil.getBooleanProperty( properties, Engine.PROP_MATCHPLURALS, true ); 108 } 109 110 /** 111 * {@inheritDoc} 112 */ 113 @Override 114 public Command findCommand( final HttpServletRequest request, final String defaultContext ) { 115 // Corner case if request is null 116 if ( request == null ) { 117 return CommandResolver.findCommand( defaultContext ); 118 } 119 120 Command command = null; 121 122 // Determine the name of the page (which may be null) 123 String pageName = extractPageFromParameter( defaultContext, request ); 124 125 // Can we find a special-page command matching the extracted page? 126 if ( pageName != null ) { 127 command = m_specialPages.get( pageName ); 128 } 129 130 // If we haven't found a matching command yet, extract the JSP path and compare to our list of special pages 131 if ( command == null ) { 132 command = extractCommandFromPath( request ); 133 134 // Otherwise: use the default context 135 if ( command == null ) { 136 command = CONTEXTS.get( defaultContext ); 137 if ( command == null ) { 138 throw new IllegalArgumentException( "Wiki context " + defaultContext + " is illegal." ); 139 } 140 } 141 } 142 143 // For PageCommand.VIEW, default to front page if a page wasn't supplied 144 if( PageCommand.VIEW.equals( command ) && pageName == null ) { 145 pageName = m_engine.getFrontPage(); 146 } 147 148 // These next blocks handle targeting requirements 149 150 // If we were passed a page parameter, try to resolve it 151 if ( command instanceof PageCommand && pageName != null ) { 152 // If there's a matching WikiPage, "wrap" the command 153 final Page page = resolvePage( request, pageName ); 154 return command.targetedCommand( page ); 155 } 156 157 // If "create group" command, target this wiki 158 final String wiki = m_engine.getApplicationName(); 159 if ( WikiCommand.CREATE_GROUP.equals( command ) ) { 160 return WikiCommand.CREATE_GROUP.targetedCommand( wiki ); 161 } 162 163 // If group command, see if we were passed a group name 164 if( command instanceof GroupCommand ) { 165 String groupName = request.getParameter( "group" ); 166 groupName = TextUtil.replaceEntities( groupName ); 167 if ( groupName != null && groupName.length() > 0 ) { 168 final GroupPrincipal group = new GroupPrincipal( groupName ); 169 return command.targetedCommand( group ); 170 } 171 } 172 173 // No page provided; return an "ordinary" command 174 return command; 175 } 176 177 /** 178 * {@inheritDoc} 179 */ 180 @Override 181 public String getFinalPageName( final String page ) throws ProviderException { 182 boolean isThere = simplePageExists( page ); 183 String finalName = page; 184 185 if ( !isThere && m_matchEnglishPlurals ) { 186 if ( page.endsWith( "s" ) ) { 187 finalName = page.substring( 0, page.length() - 1 ); 188 } else { 189 finalName += "s"; 190 } 191 192 isThere = simplePageExists( finalName ); 193 } 194 195 if( !isThere ) { 196 finalName = MarkupParser.wikifyLink( page ); 197 isThere = simplePageExists(finalName); 198 199 if( !isThere && m_matchEnglishPlurals ) { 200 if( finalName.endsWith( "s" ) ) { 201 finalName = finalName.substring( 0, finalName.length() - 1 ); 202 } else { 203 finalName += "s"; 204 } 205 206 isThere = simplePageExists( finalName ); 207 } 208 } 209 210 return isThere ? finalName : null; 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override 217 public String getSpecialPageReference( final String page ) { 218 final Command command = m_specialPages.get( page ); 219 if ( command != null ) { 220 return m_engine.getManager( URLConstructor.class ).makeURL( command.getRequestContext(), command.getURLPattern(), null ); 221 } 222 223 return null; 224 } 225 226 /** 227 * Extracts a Command based on the JSP path of an HTTP request. If the JSP requested matches a Command's <code>getJSP()</code> 228 * value, that Command is returned. 229 * 230 * @param request the HTTP request 231 * @return the resolved Command, or <code>null</code> if not found 232 */ 233 protected Command extractCommandFromPath( final HttpServletRequest request ) { 234 String jsp = request.getServletPath(); 235 236 // Take everything to right of initial / and left of # or ? 237 final int hashMark = jsp.indexOf( '#' ); 238 if ( hashMark != -1 ) { 239 jsp = jsp.substring( 0, hashMark ); 240 } 241 final int questionMark = jsp.indexOf( '?' ); 242 if ( questionMark != -1 ) { 243 jsp = jsp.substring( 0, questionMark ); 244 } 245 if ( jsp.startsWith( "/" ) ) { 246 jsp = jsp.substring( 1 ); 247 } 248 249 // Find special page reference? 250 for( final Map.Entry< String, Command > entry : m_specialPages.entrySet() ) { 251 final Command specialCommand = entry.getValue(); 252 if( specialCommand.getJSP().equals( jsp ) ) { 253 return specialCommand; 254 } 255 } 256 257 // Still haven't found a matching command? Ok, see if we match against our standard list of JSPs 258 if ( jsp.length() > 0 && JSPS.containsKey( jsp ) ) { 259 return JSPS.get( jsp ); 260 } 261 262 return null; 263 } 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override 269 public String extractPageFromParameter( final String requestContext, final HttpServletRequest request ) { 270 // Extract the page name from the URL directly 271 try { 272 String page = m_engine.getManager( URLConstructor.class ).parsePage( requestContext, request, m_engine.getContentEncoding() ); 273 if ( page != null ) { 274 try { 275 // Look for singular/plural variants; if one not found, take the one the user supplied 276 final String finalPage = getFinalPageName( page ); 277 if ( finalPage != null ) { 278 page = finalPage; 279 } 280 } catch( final ProviderException e ) { 281 // FIXME: Should not ignore! 282 } 283 return page; 284 } 285 } catch( final IOException e ) { 286 LOG.error( "Unable to create context", e ); 287 throw new InternalWikiException( "Big internal booboo, please check logs." , e ); 288 } 289 290 // Didn't resolve; return null 291 return null; 292 } 293 294 /** 295 * {@inheritDoc} 296 */ 297 @Override 298 public Page resolvePage( final HttpServletRequest request, String page ) { 299 // See if the user included a version parameter 300 int version = WikiProvider.LATEST_VERSION; 301 final String rev = request.getParameter( "version" ); 302 if ( rev != null ) { 303 try { 304 version = Integer.parseInt( rev ); 305 } catch( final NumberFormatException e ) { 306 // This happens a lot with bots or other guys who are trying to test if we are vulnerable to e.g. XSS attacks. We catch 307 // it here so that the admin does not get tons of mail. 308 } 309 } 310 311 Page wikipage = m_engine.getManager( PageManager.class ).getPage( page, version ); 312 if ( wikipage == null ) { 313 page = MarkupParser.cleanLink( page ); 314 wikipage = Wiki.contents().page( m_engine, page ); 315 } 316 return wikipage; 317 } 318 319 /** 320 * Determines whether a "page" exists by examining the list of special pages and querying the page manager. 321 * 322 * @param page the page to seek 323 * @return <code>true</code> if the page exists, <code>false</code> otherwise 324 * @throws ProviderException if the underlyng page provider that locates pages 325 * throws an exception 326 */ 327 protected boolean simplePageExists( final String page ) throws ProviderException { 328 if ( m_specialPages.containsKey( page ) ) { 329 return true; 330 } 331 return m_engine.getManager( PageManager.class ).pageExists( page ); 332 } 333 334}