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}