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 157 for( final String value : THE_BIG_NO_NO_LIST ) { 158 if( name.equals( value ) ) { 159 return ""; // FIXME: Should this be something different? 160 } 161 } 162 163 try { 164 // 165 // Using reflection to get system variables adding a new system variable 166 // now only involves creating a new method in the SystemVariables class 167 // with a name starting with get and the first character of the name of 168 // the variable capitalized. Example: 169 // public String getMysysvar(){ 170 // return "Hello World"; 171 // } 172 // 173 final SystemVariables sysvars = new SystemVariables( context ); 174 final String methodName = "get" + Character.toUpperCase( name.charAt( 0 ) ) + name.substring( 1 ); 175 final Method method = sysvars.getClass().getMethod( methodName ); 176 return ( String )method.invoke( sysvars ); 177 } catch( final NoSuchMethodException e1 ) { 178 // 179 // It is not a system var. Time to handle the other cases. 180 // 181 // Check if such a context variable exists, returning its string representation. 182 // 183 if( ( context.getVariable( varName ) ) != null ) { 184 return context.getVariable( varName ).toString(); 185 } 186 187 // 188 // Well, I guess it wasn't a final straw. We also allow variables from the session and the request (in this order). 189 // 190 final HttpServletRequest req = context.getHttpRequest(); 191 if( req != null && req.getSession() != null ) { 192 final HttpSession session = req.getSession(); 193 194 try { 195 String s = ( String )session.getAttribute( varName ); 196 197 if( s != null ) { 198 return s; 199 } 200 201 s = context.getHttpParameter( varName ); 202 if( s != null ) { 203 return s; 204 } 205 } catch( final ClassCastException e ) { 206 LOG.debug( "Not a String: " + varName ); 207 } 208 } 209 210 // 211 // And the final straw: see if the current page has named metadata. 212 // 213 final Page pg = context.getPage(); 214 if( pg != null ) { 215 final Object metadata = pg.getAttribute( varName ); 216 if( metadata != null ) { 217 return metadata.toString(); 218 } 219 } 220 221 // 222 // And the final straw part 2: see if the "real" current page has named metadata. This allows 223 // a parent page to control a inserted page through defining variables 224 // 225 final Page rpg = context.getRealPage(); 226 if( rpg != null ) { 227 final Object metadata = rpg.getAttribute( varName ); 228 if( metadata != null ) { 229 return metadata.toString(); 230 } 231 } 232 233 // 234 // Next-to-final straw: attempt to fetch using property name. We don't allow fetching any other 235 // properties than those starting with "jspwiki.". I know my own code, but I can't vouch for bugs 236 // in other people's code... :-) 237 // 238 if( varName.startsWith("jspwiki.") ) { 239 final Properties props = context.getEngine().getWikiProperties(); 240 final String s = props.getProperty( varName ); 241 if( s != null ) { 242 return s; 243 } 244 } 245 246 // 247 // Final defaults for some known quantities. 248 // 249 if( varName.equals( VAR_ERROR ) || varName.equals( VAR_MSG ) ) { 250 return ""; 251 } 252 253 throw new NoSuchVariableException( "No variable " + varName + " defined." ); 254 } catch( final Exception e ) { 255 LOG.info("Interesting exception: cannot fetch variable value", e ); 256 } 257 return ""; 258 } 259 260 /** 261 * This class provides the implementation for the different system variables. 262 * It is called via Reflection - any access to a variable called $xxx is mapped 263 * to getXxx() on this class. 264 * <p> 265 * This is a lot neater than using a huge if-else if branching structure 266 * that we used to have before. 267 * <p> 268 * Note that since we are case insensitive for variables, and VariableManager 269 * calls var.toLowerCase(), the getters for the variables do not have 270 * capitalization anywhere. This may look a bit odd, but then again, this 271 * is not meant to be a public class. 272 * 273 * @since 2.7.0 274 */ 275 @SuppressWarnings( "unused" ) 276 private static class SystemVariables { 277 278 private final Context m_context; 279 280 public SystemVariables( final Context context ) 281 { 282 m_context=context; 283 } 284 285 public String getPagename() 286 { 287 return m_context.getPage().getName(); 288 } 289 290 public String getApplicationname() 291 { 292 return m_context.getEngine().getApplicationName(); 293 } 294 295 public String getJspwikiversion() 296 { 297 return Release.getVersionString(); 298 } 299 300 public String getEncoding() { 301 return m_context.getEngine().getContentEncoding().displayName(); 302 } 303 304 public String getTotalpages() { 305 return Integer.toString( m_context.getEngine().getManager( PageManager.class ).getTotalPageCount() ); 306 } 307 308 public String getPageprovider() { 309 return m_context.getEngine().getManager( PageManager.class ).getCurrentProvider(); 310 } 311 312 public String getPageproviderdescription() { 313 return m_context.getEngine().getManager( PageManager.class ).getProviderDescription(); 314 } 315 316 public String getAttachmentprovider() { 317 final WikiProvider p = m_context.getEngine().getManager( AttachmentManager.class ).getCurrentProvider(); 318 return (p != null) ? p.getClass().getName() : "-"; 319 } 320 321 public String getAttachmentproviderdescription() { 322 final WikiProvider p = m_context.getEngine().getManager( AttachmentManager.class ).getCurrentProvider(); 323 return (p != null) ? p.getProviderInfo() : "-"; 324 } 325 326 public String getInterwikilinks() { 327 328 return m_context.getEngine().getAllInterWikiLinks().stream().map(link -> link + " --> " + m_context.getEngine().getInterWikiURL(link)).collect(Collectors.joining(", ")); 329 } 330 331 public String getInlinedimages() { 332 333 return m_context.getEngine().getAllInlinedImagePatterns().stream().collect(Collectors.joining(", ")); 334 } 335 336 public String getPluginpath() { 337 final String s = m_context.getEngine().getPluginSearchPath(); 338 339 return ( s == null ) ? "-" : s; 340 } 341 342 public String getBaseurl() 343 { 344 return m_context.getEngine().getBaseURL(); 345 } 346 347 public String getUptime() { 348 final Date now = new Date(); 349 long secondsRunning = ( now.getTime() - m_context.getEngine().getStartTime().getTime() ) / 1_000L; 350 351 final long seconds = secondsRunning % 60; 352 final long minutes = (secondsRunning /= 60) % 60; 353 final long hours = (secondsRunning /= 60) % 24; 354 final long days = secondsRunning /= 24; 355 356 return days + "d, " + hours + "h " + minutes + "m " + seconds + "s"; 357 } 358 359 public String getLoginstatus() { 360 final Session session = m_context.getWikiSession(); 361 return Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE ).getString( "varmgr." + session.getStatus() ); 362 } 363 364 public String getUsername() { 365 final Principal wup = m_context.getCurrentUser(); 366 final ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE ); 367 return wup != null ? wup.getName() : rb.getString( "varmgr.not.logged.in" ); 368 } 369 370 public String getRequestcontext() 371 { 372 return m_context.getRequestContext(); 373 } 374 375 public String getPagefilters() { 376 final FilterManager fm = m_context.getEngine().getManager( FilterManager.class ); 377 final List< PageFilter > filters = fm.getFilterList(); 378 final StringBuilder sb = new StringBuilder(); 379 for( final PageFilter pf : filters ) { 380 final String f = pf.getClass().getName(); 381 if( pf instanceof InternalModule ) { 382 continue; 383 } 384 385 if( sb.length() > 0 ) { 386 sb.append( ", " ); 387 } 388 sb.append( f ); 389 } 390 return sb.toString(); 391 } 392 } 393 394}