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