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.variables; 020 021import org.apache.logging.log4j.LogManager; 022import org.apache.logging.log4j.Logger; 023import org.apache.wiki.api.Release; 024import org.apache.wiki.api.core.Context; 025import org.apache.wiki.api.core.Page; 026import org.apache.wiki.api.core.Session; 027import org.apache.wiki.api.exceptions.NoSuchVariableException; 028import org.apache.wiki.api.filters.PageFilter; 029import org.apache.wiki.api.providers.WikiProvider; 030import org.apache.wiki.attachment.AttachmentManager; 031import org.apache.wiki.filters.FilterManager; 032import org.apache.wiki.i18n.InternationalizationManager; 033import org.apache.wiki.modules.InternalModule; 034import org.apache.wiki.pages.PageManager; 035import org.apache.wiki.preferences.Preferences; 036 037import javax.servlet.http.HttpServletRequest; 038import javax.servlet.http.HttpSession; 039import java.lang.reflect.Method; 040import java.security.Principal; 041import java.util.Date; 042import java.util.List; 043import java.util.Properties; 044import java.util.ResourceBundle; 045import java.util.stream.Collectors; 046 047 048/** 049 * Manages variables. Variables are case-insensitive. A list of all available variables is on a Wiki page called "WikiVariables". 050 * 051 * @since 1.9.20. 052 */ 053public class DefaultVariableManager implements VariableManager { 054 055 private static final Logger LOG = LogManager.getLogger( DefaultVariableManager.class ); 056 057 /** 058 * Contains a list of those properties that shall never be shown. Put names here in lower case. 059 */ 060 static final String[] THE_BIG_NO_NO_LIST = { 061 "jspwiki.auth.masterpassword" 062 }; 063 064 /** 065 * Creates a VariableManager object using the property list given. 066 * @param props The properties. 067 */ 068 public DefaultVariableManager( final Properties props ) { 069 } 070 071 /** 072 * {@inheritDoc} 073 */ 074 @Override 075 public String parseAndGetValue( final Context context, final String link ) throws IllegalArgumentException, NoSuchVariableException { 076 if( !link.startsWith( "{$" ) ) { 077 throw new IllegalArgumentException( "Link does not start with {$" ); 078 } 079 if( !link.endsWith( "}" ) ) { 080 throw new IllegalArgumentException( "Link does not end with }" ); 081 } 082 final String varName = link.substring( 2, link.length() - 1 ); 083 084 return getValue( context, varName.trim() ); 085 } 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override 091 // FIXME: somewhat slow. 092 public String expandVariables( final Context context, final String source ) { 093 final StringBuilder result = new StringBuilder(); 094 for( int i = 0; i < source.length(); i++ ) { 095 if( source.charAt(i) == '{' ) { 096 if( i < source.length()-2 && source.charAt(i+1) == '$' ) { 097 final int end = source.indexOf( '}', i ); 098 099 if( end != -1 ) { 100 final String varname = source.substring( i+2, end ); 101 String value; 102 103 try { 104 value = getValue( context, varname ); 105 } catch( final NoSuchVariableException | IllegalArgumentException e ) { 106 value = e.getMessage(); 107 } 108 109 result.append( value ); 110 i = end; 111 } 112 } else { 113 result.append( '{' ); 114 } 115 } else { 116 result.append( source.charAt(i) ); 117 } 118 } 119 120 return result.toString(); 121 } 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public String getValue( final Context context, final String varName, final String defValue ) { 128 try { 129 return getValue( context, varName ); 130 } catch( final NoSuchVariableException e ) { 131 return defValue; 132 } 133 } 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override 139 public String getVariable( final Context context, final String name ) { 140 return getValue( context, name, null ); 141 } 142 143 /** 144 * {@inheritDoc} 145 */ 146 @Override 147 public String getValue( final Context context, final String varName ) throws IllegalArgumentException, NoSuchVariableException { 148 if( varName == null ) { 149 throw new IllegalArgumentException( "Null variable name." ); 150 } 151 if( varName.isEmpty() ) { 152 throw new IllegalArgumentException( "Zero length variable name." ); 153 } 154 // Faster than doing equalsIgnoreCase() 155 final String name = varName.toLowerCase(); 156 if (!"jspwiki.frontpage".equals(name) && 157 !"jspwiki.runfilters".equals(name) && 158 name.startsWith( "jspwiki" ) ) { 159 String whitelist = context.getEngine().getWikiProperties().getProperty("jspwiki.variablemanager.whitelist"); 160 if (whitelist!=null && !whitelist.contains(name)) { 161 LOG.warn("variable manager is denying access to '" + name + "'. to override this behavior, " 162 + "you can add this to jspwiki.variablemanager.whitelist in the properties file."); 163 return ""; 164 } 165 166 } 167 for( final String value : THE_BIG_NO_NO_LIST ) { 168 if( name.equals( value ) ) { 169 return ""; // FIXME: Should this be something different? 170 } 171 } 172 173 try { 174 // 175 // Using reflection to get system variables adding a new system variable 176 // now only involves creating a new method in the SystemVariables class 177 // with a name starting with get and the first character of the name of 178 // the variable capitalized. Example: 179 // public String getMysysvar(){ 180 // return "Hello World"; 181 // } 182 // 183 final SystemVariables sysvars = new SystemVariables( context ); 184 final String methodName = "get" + Character.toUpperCase( name.charAt( 0 ) ) + name.substring( 1 ); 185 final Method method = sysvars.getClass().getMethod( methodName ); 186 return ( String )method.invoke( sysvars ); 187 } catch( final NoSuchMethodException e1 ) { 188 // 189 // It is not a system var. Time to handle the other cases. 190 // 191 // Check if such a context variable exists, returning its string representation. 192 // 193 if( ( context.getVariable( varName ) ) != null ) { 194 return context.getVariable( varName ).toString(); 195 } 196 197 // 198 // Well, I guess it wasn't a final straw. We also allow variables from the session and the request (in this order). 199 // 200 final HttpServletRequest req = context.getHttpRequest(); 201 if( req != null && req.getSession() != null ) { 202 final HttpSession session = req.getSession(); 203 204 try { 205 String s = ( String )session.getAttribute( varName ); 206 207 if( s != null ) { 208 return s; 209 } 210 211 s = context.getHttpParameter( varName ); 212 if( s != null ) { 213 return s; 214 } 215 } catch( final ClassCastException e ) { 216 LOG.debug( "Not a String: " + varName ); 217 } 218 } 219 220 // 221 // And the final straw: see if the current page has named metadata. 222 // 223 final Page pg = context.getPage(); 224 if( pg != null ) { 225 final Object metadata = pg.getAttribute( varName ); 226 if( metadata != null ) { 227 return metadata.toString(); 228 } 229 } 230 231 // 232 // And the final straw part 2: see if the "real" current page has named metadata. This allows 233 // a parent page to control a inserted page through defining variables 234 // 235 final Page rpg = context.getRealPage(); 236 if( rpg != null ) { 237 final Object metadata = rpg.getAttribute( varName ); 238 if( metadata != null ) { 239 return metadata.toString(); 240 } 241 } 242 243 // 244 // Next-to-final straw: attempt to fetch using property name. We don't allow fetching any other 245 // properties than those starting with "jspwiki.". I know my own code, but I can't vouch for bugs 246 // in other people's code... :-) 247 // 248 if( varName.startsWith("jspwiki.") ) { 249 final Properties props = context.getEngine().getWikiProperties(); 250 final String s = props.getProperty( varName ); 251 if( s != null ) { 252 return s; 253 } 254 } 255 256 // 257 // Final defaults for some known quantities. 258 // 259 if( varName.equals( VAR_ERROR ) || varName.equals( VAR_MSG ) ) { 260 return ""; 261 } 262 263 throw new NoSuchVariableException( "No variable " + varName + " defined." ); 264 } catch( final Exception e ) { 265 LOG.info("Interesting exception: cannot fetch variable value", e ); 266 } 267 return ""; 268 } 269 270 /** 271 * This class provides the implementation for the different system variables. 272 * It is called via Reflection - any access to a variable called $xxx is mapped 273 * to getXxx() on this class. 274 * <p> 275 * This is a lot neater than using a huge if-else if branching structure 276 * that we used to have before. 277 * <p> 278 * Note that since we are case insensitive for variables, and VariableManager 279 * calls var.toLowerCase(), the getters for the variables do not have 280 * capitalization anywhere. This may look a bit odd, but then again, this 281 * is not meant to be a public class. 282 * 283 * @since 2.7.0 284 */ 285 @SuppressWarnings( "unused" ) 286 private static class SystemVariables { 287 288 private final Context m_context; 289 290 public SystemVariables( final Context context ) 291 { 292 m_context=context; 293 } 294 295 public String getPagename() 296 { 297 return m_context.getPage().getName(); 298 } 299 300 public String getApplicationname() 301 { 302 return m_context.getEngine().getApplicationName(); 303 } 304 305 public String getJspwikiversion() 306 { 307 return Release.getVersionString(); 308 } 309 310 public String getEncoding() { 311 return m_context.getEngine().getContentEncoding().displayName(); 312 } 313 314 public String getTotalpages() { 315 return Integer.toString( m_context.getEngine().getManager( PageManager.class ).getTotalPageCount() ); 316 } 317 318 public String getPageprovider() { 319 return m_context.getEngine().getManager( PageManager.class ).getCurrentProvider(); 320 } 321 322 public String getPageproviderdescription() { 323 return m_context.getEngine().getManager( PageManager.class ).getProviderDescription(); 324 } 325 326 public String getAttachmentprovider() { 327 final WikiProvider p = m_context.getEngine().getManager( AttachmentManager.class ).getCurrentProvider(); 328 return (p != null) ? p.getClass().getName() : "-"; 329 } 330 331 public String getAttachmentproviderdescription() { 332 final WikiProvider p = m_context.getEngine().getManager( AttachmentManager.class ).getCurrentProvider(); 333 return (p != null) ? p.getProviderInfo() : "-"; 334 } 335 336 public String getInterwikilinks() { 337 338 return m_context.getEngine().getAllInterWikiLinks().stream().map(link -> link + " --> " + m_context.getEngine().getInterWikiURL(link)).collect(Collectors.joining(", ")); 339 } 340 341 public String getInlinedimages() { 342 343 return m_context.getEngine().getAllInlinedImagePatterns().stream().collect(Collectors.joining(", ")); 344 } 345 346 public String getPluginpath() { 347 final String s = m_context.getEngine().getPluginSearchPath(); 348 349 return ( s == null ) ? "-" : s; 350 } 351 352 public String getBaseurl() 353 { 354 return m_context.getEngine().getBaseURL(); 355 } 356 357 public String getUptime() { 358 final Date now = new Date(); 359 long secondsRunning = ( now.getTime() - m_context.getEngine().getStartTime().getTime() ) / 1_000L; 360 361 final long seconds = secondsRunning % 60; 362 final long minutes = (secondsRunning /= 60) % 60; 363 final long hours = (secondsRunning /= 60) % 24; 364 final long days = secondsRunning /= 24; 365 366 return days + "d, " + hours + "h " + minutes + "m " + seconds + "s"; 367 } 368 369 public String getLoginstatus() { 370 final Session session = m_context.getWikiSession(); 371 return Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE ).getString( "varmgr." + session.getStatus() ); 372 } 373 374 public String getUsername() { 375 final Principal wup = m_context.getCurrentUser(); 376 final ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE ); 377 return wup != null ? wup.getName() : rb.getString( "varmgr.not.logged.in" ); 378 } 379 380 public String getRequestcontext() 381 { 382 return m_context.getRequestContext(); 383 } 384 385 public String getPagefilters() { 386 final FilterManager fm = m_context.getEngine().getManager( FilterManager.class ); 387 final List< PageFilter > filters = fm.getFilterList(); 388 final StringBuilder sb = new StringBuilder(); 389 for( final PageFilter pf : filters ) { 390 final String f = pf.getClass().getName(); 391 if( pf instanceof InternalModule ) { 392 continue; 393 } 394 395 if( sb.length() > 0 ) { 396 sb.append( ", " ); 397 } 398 sb.append( f ); 399 } 400 return sb.toString(); 401 } 402 } 403 404}