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.url;
020
021import java.io.UnsupportedEncodingException;
022import java.net.MalformedURLException;
023import java.net.URL;
024import java.util.Properties;
025
026import javax.servlet.http.HttpServletRequest;
027
028import org.apache.commons.lang.StringUtils;
029
030import org.apache.wiki.WikiContext;
031import org.apache.wiki.WikiEngine;
032import org.apache.wiki.ui.Command;
033import org.apache.wiki.ui.CommandResolver;
034import org.apache.wiki.util.TextUtil;
035
036/**
037 *  Implements the default URL constructor using links directly to the
038 *  JSP pages.  This is what JSPWiki by default is using.  For example,
039 *  WikiContext.VIEW points at "Wiki.jsp", etc.
040 *  
041 *  @since 2.2
042 */
043public class DefaultURLConstructor
044    implements URLConstructor
045{
046    protected WikiEngine m_engine;
047
048    /**
049     *  Contains the absolute path of the JSPWiki Web application without the
050     *  actual servlet (which is the m_urlPrefix).
051     */
052    protected String m_pathPrefix = "";
053    
054    /**
055     * 
056     * {@inheritDoc}
057     */
058    public void initialize( WikiEngine engine, 
059                            Properties properties )
060    {
061        m_engine = engine;
062
063        String baseurl = engine.getBaseURL();
064
065        if( baseurl != null && baseurl.length() > 0 )
066        {
067            try
068            {
069                URL url = new URL( baseurl );
070        
071                String path = url.getPath();
072        
073                m_pathPrefix = path;
074            }
075            catch( MalformedURLException e )
076            {
077                m_pathPrefix = "/JSPWiki/"; // Just a guess.
078            }
079        }
080    }
081
082    /**
083     *  Does replacement of some particular variables.  The variables are:
084     *  
085     *  <ul>
086     *  <li> "%u" - inserts either the base URL (when absolute is required), or the base path
087     *       (which is an absolute path without the host name).
088     *  <li> "%U" - always inserts the base URL
089     *  <li> "%p" - always inserts the base path
090     *  <li> "%n" - inserts the page name
091     *  </ul>
092     *  
093     * @param baseptrn  The pattern to use
094     * @param name The page name
095     * @param absolute If true, %u is always the entire base URL, otherwise it depends on
096     *                 the setting in jspwiki.properties.
097     * @return A replacement.
098     */
099    protected final String doReplacement( String baseptrn, String name, boolean absolute )
100    {
101        String baseurl = m_pathPrefix;
102
103        if( absolute ) baseurl = m_engine.getBaseURL();
104
105        baseptrn = TextUtil.replaceString( baseptrn, "%u", baseurl );
106        baseptrn = TextUtil.replaceString( baseptrn, "%U", m_engine.getBaseURL() );
107        baseptrn = TextUtil.replaceString( baseptrn, "%n", encodeURI(name) );
108        baseptrn = TextUtil.replaceString( baseptrn, "%p", m_pathPrefix );
109
110        return baseptrn;
111    }
112
113    /**
114     *  URLEncoder returns pluses, when we want to have the percent
115     *  encoding.  See http://issues.apache.org/bugzilla/show_bug.cgi?id=39278
116     *  for more info.
117     *  
118     *  We also convert any %2F's back to slashes to make nicer-looking URLs.
119     */
120    private String encodeURI( String uri )
121    {
122        uri = m_engine.encodeName(uri);
123        
124        uri = StringUtils.replace( uri, "+", "%20" );
125        uri = StringUtils.replace( uri, "%2F", "/" );
126        
127        return uri;
128    }
129    
130    /**
131     * Returns the URL pattern for a supplied wiki request context.
132     * @param context the wiki context
133     * @param name the wiki page
134     * @return A pattern for replacement.
135     * @throws IllegalArgumentException if the context cannot be found
136     */
137    public static String getURLPattern( String context, String name )
138        throws IllegalArgumentException
139    {
140        if( context.equals(WikiContext.VIEW) && name == null)
141        {
142            // FIXME
143            return "%uWiki.jsp";
144        }
145        
146        // Find the action matching our pattern (could throw exception)
147        Command command = CommandResolver.findCommand( context );
148        
149        return command.getURLPattern();
150    }
151    
152    /**
153     *  Constructs the actual URL based on the context.
154     */
155    private String makeURL( String context,
156                            String name,
157                            boolean absolute )
158    {
159        return doReplacement( getURLPattern(context,name), name, absolute );
160    }
161
162    /**
163     *  Constructs the URL with a bunch of parameters.
164     *  @param parameters If null or empty, no parameters are added.
165     *  
166     *  {@inheritDoc}
167     */
168    public String makeURL( String context,
169                           String name,
170                           boolean absolute,
171                           String parameters )
172    {
173        if( parameters != null && parameters.length() > 0 )
174        {            
175            if( context.equals(WikiContext.ATTACH) )
176            {
177                parameters = "?"+parameters;
178            }
179            else if( context.equals(WikiContext.NONE) )
180            {
181                parameters = (name.indexOf('?') != -1 ) ? "&amp;" : "?" + parameters;
182            }
183            else
184            {
185                parameters = "&amp;"+parameters;
186            }
187        }
188        else
189        {
190            parameters = "";
191        }
192        return makeURL( context, name, absolute )+parameters;
193    }
194
195    /**
196     *  Should parse the "page" parameter from the actual
197     *  request.
198     *  
199     *  {@inheritDoc}
200     */
201    public String parsePage( String context,
202                             HttpServletRequest request,
203                             String encoding )
204        throws UnsupportedEncodingException
205    {
206        String pagereq = request.getParameter( "page" );
207
208        if( context.equals(WikiContext.ATTACH) )
209        {
210            pagereq = parsePageFromURL( request, encoding );
211        }
212
213        return pagereq;
214    }
215
216    /**
217     *  There's a bug in Tomcat until 5.5.16 at least: The "+" sign is not
218     *  properly decoded by the servlet container, and therefore request.getPathInfo()
219     *  will return faulty results for paths which contains + signs to signify spaces.
220     *  <p>
221     *  This method provides a workaround by simply parsing the getRequestURI(), which
222     *  is returned from the servlet container undedecoded.
223     *  <p>
224     *  Please see <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=39278">Tomcat Bug 39278</a>
225     *  for more information.
226     *  
227     *  @param request A HTTP servlet request
228     *  @param encoding The used encoding
229     *  @return a String, decoded by JSPWiki, specifying extra path information that comes 
230     *          after the servlet path but before the query string in the request URL; 
231     *          or null if the URL does not have any extra path information
232     *  @throws UnsupportedEncodingException
233     */
234    /*
235    private static String getPathInfo( HttpServletRequest request, String encoding )
236        throws UnsupportedEncodingException
237    {
238        String c = request.getContextPath(); // Undecoded
239        String s = request.getServletPath(); // Decoded
240        String u = request.getRequestURI();  // Undecoded
241        
242        c = URLDecoder.decode( c, encoding );
243        u = URLDecoder.decode( u, encoding );
244        
245        String pi = u.substring( s.length()+c.length() );
246        
247        if( pi.length() == 0 ) pi = null;
248        
249        return pi;
250    }
251    */
252    /**
253     *  Takes the name of the page from the request URI.
254     *  The initial slash is also removed.  If there is no page,
255     *  returns null.
256     *  
257     *  @param request The request to parse
258     *  @param encoding The encoding to use
259     *  
260     *  @return a parsed page name, or null, if it cannot be found
261     *  
262     *  @throws UnsupportedEncodingException If the encoding is not recognized.
263     */
264    public static String parsePageFromURL( HttpServletRequest request,
265                                           String encoding )
266        throws UnsupportedEncodingException
267    {
268        String name = request.getPathInfo();
269
270        if( name == null || name.length() <= 1 )
271        {
272            return null;
273        }
274        else if( name.charAt(0) == '/' )
275        {
276            name = name.substring(1);
277        }
278       
279        //
280        //  This is required, because by default all URLs are handled
281        //  as Latin1, even if they are really UTF-8.
282        //
283        
284        // name = TextUtil.urlDecode( name, encoding );
285        
286        return name;
287    }
288
289    
290    /**
291     *  This method is not needed for the DefaultURLConstructor.
292     *  
293     * @param request The HTTP Request that was used to end up in this page.
294     * @return "Wiki.jsp", "PageInfo.jsp", etc.  Just return the name,
295     *         JSPWiki will figure out the page.
296     */
297    public String getForwardPage( HttpServletRequest request )
298    {
299        return request.getPathInfo();
300    }
301}