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