    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}