    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}