001package org.apache.turbine.util.uri;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.UnsupportedEncodingException;
023import java.net.URLEncoder;
024import java.nio.charset.Charset;
025import java.nio.charset.IllegalCharsetNameException;
026import java.nio.charset.UnsupportedCharsetException;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.List;
030import java.util.Objects;
031import java.util.stream.Collectors;
032
033import org.apache.commons.lang3.StringUtils;
034import org.apache.fulcrum.parser.ParameterParser;
035import org.apache.fulcrum.parser.ParserService;
036import org.apache.logging.log4j.LogManager;
037import org.apache.logging.log4j.Logger;
038import org.apache.turbine.services.TurbineServices;
039import org.apache.turbine.util.RunData;
040import org.apache.turbine.util.ServerData;
041
042/**
043 * This class allows you to keep all the information needed for a single
044 * link at one place. It keeps your query data, path info, the server
045 * scheme, name, port and the script path.
046 *
047 * If you must generate a Turbine Link, use this class.
048 *
049 * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
050 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
051 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
052 * @version $Id$
053 */
054
055public class TurbineURI
056        extends BaseURI
057{
058    /** Logging */
059    private static final Logger log = LogManager.getLogger(TurbineURI.class);
060
061    /** Contains the PathInfo and QueryData vectors */
062    private List<URIParam> [] dataVectors = null;
063
064    /** Local reference to the parser service for URI parameter folding */
065    private ParserService parserService;
066
067    /** URI Parameter encoding as defined by the parser service */
068    private Charset parameterEncoding;
069
070    /*
071     * ========================================================================
072     *
073     * Constructors
074     *
075     * ========================================================================
076     *
077     */
078
079    /**
080     * Empty C'tor. Uses Turbine.getDefaultServerData().
081     */
082    public TurbineURI()
083    {
084        super();
085        init();
086    }
087
088    /**
089     * Constructor with a RunData object.
090     *
091     * @param runData A RunData object
092     */
093    public TurbineURI(RunData runData)
094    {
095        super(runData);
096        init();
097    }
098
099    /**
100     * Constructor, set explicit redirection.
101     *
102     * @param runData A RunData object
103     * @param redirect True if redirection allowed.
104     */
105    public TurbineURI(RunData runData, boolean redirect)
106    {
107        super(runData, redirect);
108        init();
109    }
110
111    /**
112     * Constructor, set Screen.
113     *
114     * @param runData A RunData object
115     * @param screen A Screen Name
116     */
117    public TurbineURI(RunData runData, String screen)
118    {
119        this(runData);
120        setScreen(screen);
121    }
122
123    /**
124     * Constructor, set Screen, set explicit redirection.
125     *
126     * @param runData A RunData object
127     * @param screen A Screen Name
128     * @param redirect True if redirection allowed.
129     */
130    public TurbineURI(RunData runData, String screen, boolean redirect)
131    {
132        this(runData, redirect);
133        setScreen(screen);
134    }
135
136    /**
137     * Constructor, set Screen and Action.
138     *
139     * @param runData A RunData object
140     * @param screen A Screen Name
141     * @param action An Action Name
142     */
143    public TurbineURI(RunData runData, String screen, String action)
144    {
145        this(runData, screen);
146        setAction(action);
147    }
148
149    /**
150     * Constructor, set Screen and Action, set explicit redirection.
151     *
152     * @param runData A RunData object
153     * @param screen A Screen Name
154     * @param action An Action Name
155     * @param redirect True if redirection allowed.
156     */
157    public TurbineURI(RunData runData, String screen, String action, boolean redirect)
158    {
159        this(runData, screen, redirect);
160        setAction(action);
161    }
162
163    /**
164     * Constructor with a ServerData object.
165     *
166     * @param serverData A ServerData object
167     */
168    public TurbineURI(ServerData serverData)
169    {
170        super(serverData);
171        init();
172    }
173
174    /**
175     * Constructor, set explicit redirection.
176     *
177     * @param serverData A ServerData object
178     * @param redirect True if redirection allowed.
179     */
180    public TurbineURI(ServerData serverData, boolean redirect)
181    {
182        super(serverData, redirect);
183        init();
184    }
185
186    /**
187     * Constructor, set Screen.
188     *
189     * @param serverData A ServerData object
190     * @param screen A Screen Name
191     */
192    public TurbineURI(ServerData serverData, String screen)
193    {
194        this(serverData);
195        setScreen(screen);
196    }
197
198    /**
199     * Constructor, set Screen, set explicit redirection.
200     *
201     * @param serverData A ServerData object
202     * @param screen A Screen Name
203     * @param redirect True if redirection allowed.
204     */
205    public TurbineURI(ServerData serverData, String screen, boolean redirect)
206    {
207        this(serverData, redirect);
208        setScreen(screen);
209    }
210
211    /**
212     * Constructor, set Screen and Action.
213     *
214     * @param serverData A ServerData object
215     * @param screen A Screen Name
216     * @param action An Action Name
217     */
218    public TurbineURI(ServerData serverData, String screen, String action)
219    {
220        this(serverData, screen);
221        setAction(action);
222    }
223
224    /**
225     * Constructor, set Screen and Action, set explicit redirection.
226     *
227     * @param serverData A ServerData object
228     * @param screen A Screen Name
229     * @param action An Action Name
230     * @param redirect True if redirection allowed.
231     */
232    public TurbineURI(ServerData serverData, String screen, String action,
233                      boolean redirect)
234    {
235        this(serverData, screen, redirect);
236        setAction(action);
237    }
238
239    /**
240     * Constructor, user Turbine.getDefaultServerData(), set Screen and Action.
241     *
242     * @param screen A Screen Name
243     * @param action An Action Name
244     */
245    public TurbineURI(String screen, String action)
246    {
247        this();
248        setScreen(screen);
249        setAction(action);
250    }
251
252    /*
253     * ========================================================================
254     *
255     * Init
256     *
257     * ========================================================================
258     *
259     */
260
261    /**
262     * Init the TurbineURI.
263     */
264    @SuppressWarnings("unchecked")
265    private void init()
266    {
267        dataVectors = new List[2];
268        dataVectors[PATH_INFO]  = new ArrayList<>();
269        dataVectors[QUERY_DATA] = new ArrayList<>();
270        parserService = (ParserService)TurbineServices.getInstance().getService(ParserService.ROLE);
271
272        try
273        {
274            parameterEncoding = Charset.forName(parserService.getParameterEncoding());
275        }
276        catch (IllegalCharsetNameException | UnsupportedCharsetException e)
277        {
278            log.error("Unsupported encoding {}", parserService.getParameterEncoding(), e);
279        }
280    }
281
282    /**
283     * Sets the action= value for this URL.
284     *
285     * By default it adds the information to the path_info instead
286     * of the query data. An empty value (null or "") cleans out
287     * an existing value.
288     *
289     * @param action A String with the action value.
290     */
291    public void setAction(String action)
292    {
293        if(StringUtils.isNotEmpty(action))
294        {
295            add(PATH_INFO, CGI_ACTION_PARAM, action);
296        }
297        else
298        {
299            clearAction();
300        }
301    }
302
303    /**
304     * Sets the fired eventSubmit= value for this URL.
305     *
306     * @param event The event to fire.
307     *
308     */
309    public void setEvent(String event)
310    {
311        add(PATH_INFO, EVENT_PREFIX + event, event);
312    }
313
314    /**
315     * Sets the action= and eventSubmit= values for this URL.
316     *
317     * By default it adds the information to the path_info instead
318     * of the query data. An empty value (null or "") for the action cleans out
319     * an existing value.  An empty value (null or "") for the event has no
320     * effect.
321     *
322     * @param action A String with the action value.
323     * @param event A string with the event name.
324     */
325    public void setActionEvent(String action, String event)
326    {
327        setAction(action);
328        if(StringUtils.isNotEmpty(event))
329        {
330            setEvent(event);
331        }
332    }
333
334    /**
335     * Clears the action= value for this URL.
336     */
337    public void clearAction()
338    {
339        removePathInfo(CGI_ACTION_PARAM);
340    }
341
342    /**
343     * Sets the screen= value for this URL.
344     *
345     * By default it adds the information to the path_info instead
346     * of the query data. An empty value (null or "") cleans out
347     * an existing value.
348     *
349     * @param screen A String with the screen value.
350     */
351    public void setScreen(String screen)
352    {
353        if(StringUtils.isNotEmpty(screen))
354        {
355            add(PATH_INFO, CGI_SCREEN_PARAM, screen);
356        }
357        else
358        {
359            clearScreen();
360        }
361    }
362
363    /**
364     * Clears the screen= value for this URL.
365     */
366    public void clearScreen()
367    {
368        removePathInfo(CGI_SCREEN_PARAM);
369    }
370
371    /*
372     * ========================================================================
373     *
374     * Adding and removing Data from the Path Info and Query Data
375     *
376     * ========================================================================
377     */
378
379
380    /**
381     * Adds a name=value pair for every entry in a ParameterParser
382     * object to the path_info string.
383     *
384     * @param pp A ParameterParser.
385     */
386    public void addPathInfo(ParameterParser pp)
387    {
388        add(PATH_INFO, pp);
389    }
390
391    /**
392     * Adds an existing List of URIParam objects to
393     * the path_info string.
394     *
395     * @param list A list with URIParam objects.
396     */
397    public void addPathInfo(List<URIParam> list)
398    {
399        add(PATH_INFO, list);
400    }
401
402    /**
403     * Adds a name=value pair to the path_info string.
404     *
405     * @param name A String with the name to add.
406     * @param value An Object with the value to add.
407     */
408    public void addPathInfo(String name, Object value)
409    {
410        add(PATH_INFO, name, null == value ? null : value.toString());
411    }
412
413    /**
414     * Adds a name=value pair to the path_info string.
415     *
416     * @param name A String with the name to add.
417     * @param value A String with the value to add.
418     */
419    public void addPathInfo(String name, String value)
420    {
421        add(PATH_INFO, name, value);
422    }
423
424    /**
425     * Adds a name=value pair to the path_info string.
426     *
427     * @param name A String with the name to add.
428     * @param value A double with the value to add.
429     */
430    public void addPathInfo(String name, double value)
431    {
432        add(PATH_INFO, name, Double.toString(value));
433    }
434
435    /**
436     * Adds a name=value pair to the path_info string.
437     *
438     * @param name A String with the name to add.
439     * @param value An int with the value to add.
440     */
441    public void addPathInfo(String name, int value)
442    {
443        add(PATH_INFO, name, Integer.toString(value));
444    }
445
446    /**
447     * Adds a name=value pair to the path_info string.
448     *
449     * @param name A String with the name to add.
450     * @param value A long with the value to add.
451     */
452    public void addPathInfo(String name, long value)
453    {
454        add(PATH_INFO, name, Long.toString(value));
455    }
456
457    /**
458     * Adds a name=value pair to the query string.
459     *
460     * @param name A String with the name to add.
461     * @param value An Object with the value to add.
462     */
463    public void addQueryData(String name, Object value)
464    {
465        add(QUERY_DATA, name, null == value ? null : value.toString());
466    }
467
468    /**
469     * Adds a name=value pair to the query string.
470     *
471     * @param name A String with the name to add.
472     * @param value A String with the value to add.
473     */
474    public void addQueryData(String name, String value)
475    {
476        add(QUERY_DATA, name, value);
477    }
478
479    /**
480     * Adds a name=value pair to the query string.
481     *
482     * @param name A String with the name to add.
483     * @param value A double with the value to add.
484     */
485    public void addQueryData(String name, double value)
486    {
487        add(QUERY_DATA, name, Double.toString(value));
488    }
489
490    /**
491     * Adds a name=value pair to the query string.
492     *
493     * @param name A String with the name to add.
494     * @param value An int with the value to add.
495     */
496    public void addQueryData(String name, int value)
497    {
498        add(QUERY_DATA, name, Integer.toString(value));
499    }
500
501    /**
502     * Adds a name=value pair to the query string.
503     *
504     * @param name A String with the name to add.
505     * @param value A long with the value to add.
506     */
507    public void addQueryData(String name, long value)
508    {
509        add(QUERY_DATA, name, Long.toString(value));
510    }
511
512    /**
513     * Adds a name=value pair for every entry in a ParameterParser
514     * object to the query string.
515     *
516     * @param pp A ParameterParser.
517     */
518    public void addQueryData(ParameterParser pp)
519    {
520        add(QUERY_DATA, pp);
521    }
522
523    /**
524     * Adds an existing List of URIParam objects to the query data.
525     *
526     * @param list A list with URIParam objects.
527     */
528    public void addQueryData(List<URIParam> list)
529    {
530        add(QUERY_DATA, list);
531    }
532
533    /**
534     * Is Path Info data set in this URI?
535     *
536     * @return true if Path Info has values
537     */
538    public boolean hasPathInfo()
539    {
540        return !dataVectors[PATH_INFO].isEmpty();
541    }
542
543    /**
544     * Removes all the path info elements.
545     */
546    public void removePathInfo()
547    {
548        dataVectors[PATH_INFO].clear();
549    }
550
551    /**
552     * Removes a name=value pair from the path info.
553     *
554     * @param name A String with the name to be removed.
555     */
556    public void removePathInfo(String name)
557    {
558        remove(PATH_INFO, name);
559    }
560
561    /**
562     * Is Query data set in this URI?
563     *
564     * @return true if Query data has values
565     */
566    public boolean hasQueryData()
567    {
568        return !dataVectors[QUERY_DATA].isEmpty();
569    }
570
571    /**
572     * Removes all the query string elements.
573     */
574    public void removeQueryData()
575    {
576        dataVectors[QUERY_DATA].clear();
577    }
578
579    /**
580     * Removes a name=value pair from the query string.
581     *
582     * @param name A String with the name to be removed.
583     */
584    public void removeQueryData(String name)
585    {
586        remove (QUERY_DATA, name);
587    }
588
589    /**
590     * Template Link and friends want to be able to turn the encoding
591     * of the servlet container off. After calling this method,
592     * the no encoding will happen any longer. If you think, that you
593     * need this outside a template context, think again.
594     */
595    public void clearResponse()
596    {
597        setResponse(null);
598    }
599
600
601    /**
602     * Builds the URL with all of the data URL-encoded as well as
603     * encoded using HttpServletResponse.encodeUrl(). The resulting
604     * URL is absolute; it starts with http/https...
605     *
606     * <pre>
607     * TurbineURI tui = new TurbineURI (data, "UserScreen");
608     * tui.addPathInfo("user","jon");
609     * tui.getAbsoluteLink();
610     * </pre>
611     *
612     *  The above call to absoluteLink() would return the String:
613     *
614     * <p>
615     * http://www.server.com/servlets/Turbine/screen/UserScreen/user/jon
616     * </p>
617     *
618     * @return A String with the built URL.
619     */
620    public String getAbsoluteLink()
621    {
622        StringBuilder output = new StringBuilder();
623
624        getSchemeAndPort(output);
625
626        buildRelativeLink(output);
627
628        //
629        // Encode Response does all the fixup for the Servlet Container
630        //
631        return encodeResponse(output.toString());
632    }
633
634    /**
635     * Builds the URL with all of the data URL-encoded as well as
636     * encoded using HttpServletResponse.encodeUrl(). The resulting
637     * URL is relative to the webserver root.
638     *
639     * <pre>
640     * TurbineURI tui = new TurbineURI (data, "UserScreen");
641     * tui.addPathInfo("user","jon");
642     * tui.getRelativeLink();
643     * </pre>
644     *
645     *  The above call to relativeLink() would return the String:
646     *
647     * <p>
648     * /servlets/Turbine/screen/UserScreen/user/jon
649     * </p>
650     *
651     * @return A String with the built URL.
652     */
653    public String getRelativeLink()
654    {
655        StringBuilder output = new StringBuilder();
656
657        buildRelativeLink(output);
658
659        //
660        // Encode Response does all the fixup for the Servlet Container
661        //
662        return encodeResponse(output.toString());
663    }
664
665    /**
666     * Add everything needed for a relative link to the passed StringBuilder.
667     *
668     * @param output A Stringbuffer
669     */
670    private void buildRelativeLink(StringBuilder output)
671    {
672        getContextAndScript(output);
673
674        if (hasPathInfo())
675        {
676            output.append('/');
677            getPathInfoAsString(output);
678        }
679
680        if (hasReference())
681        {
682            output.append('#');
683            output.append(getReference());
684        }
685
686        if (hasQueryData())
687        {
688            output.append('?');
689            getQueryDataAsString(output);
690        }
691    }
692
693    /**
694     * Gets the current Path Info List.
695     *
696     * @return A List which contains all query data keys. The keys
697     * are URIParam objects.
698     */
699    public List<URIParam> getPathInfo()
700    {
701        return dataVectors[PATH_INFO];
702    }
703
704    /**
705     * Sets the Query Data List. Replaces the current query data list
706     * with the one supplied. The list must contain only URIParam
707     * objects!
708     *
709     * @param pathInfo A List with new param objects.
710     */
711
712    public void setPathInfo(List<URIParam> pathInfo)
713    {
714        dataVectors[PATH_INFO] = pathInfo;
715    }
716
717    /**
718     * Gets the current Query Data List.
719     *
720     * @return A List which contains all query data keys. The keys
721     * are URIParam objects.
722     */
723    public List<URIParam> getQueryData()
724    {
725        return dataVectors[QUERY_DATA];
726    }
727
728    /**
729     * Sets the Query Data List. Replaces the current query data list
730     * with the one supplied. The list must contain only URIParam
731     * objects!
732     *
733     * @param queryData A List with new param objects.
734     */
735
736    public void setQueryData(List<URIParam> queryData)
737    {
738        dataVectors[QUERY_DATA] = queryData;
739    }
740
741    /**
742     * Simply calls getAbsoluteLink(). You should not use this in your
743     * code unless you have to. Use getAbsoluteLink.
744     *
745     * @return This URI as a String
746     *
747     */
748    @Override
749    public String toString()
750    {
751        return getAbsoluteLink();
752    }
753
754    /*
755     * ========================================================================
756     *
757     * Protected / Private Methods
758     *
759     * ========================================================================
760     *
761     */
762
763    /**
764     * Returns the Path Info data as a String.
765     *
766     * @param output The StringBuilder that should hold the path info.
767     */
768    private void getPathInfoAsString(StringBuilder output)
769    {
770        doEncode(output, dataVectors[PATH_INFO], "/", "/");
771    }
772
773    /**
774     * Returns the Query data as a String.
775     *
776     * @param output The StringBuilder that should hold the query data.
777     */
778    private void getQueryDataAsString(StringBuilder output)
779    {
780        doEncode(output, dataVectors[QUERY_DATA], "&", "=");
781    }
782
783    /**
784     * URL encode the given string, catching possible Exceptions
785     *
786     * @param string the string
787     * @return the encoded string
788     */
789    private String urlEncode(String string)
790    {
791        try
792        {
793            // Java10: return URLEncoder.encode(string, parameterEncoding);
794            return URLEncoder.encode(string, parameterEncoding.name());
795        }
796        catch (UnsupportedEncodingException e)
797        {
798            log.warn("Unsupported encoding {}", parameterEncoding);
799        }
800
801        return StringUtils.EMPTY;
802    }
803
804    /**
805     * Does the actual encoding for pathInfoAsString and queryDataAsString.
806     *
807     * @param output The String builder that should contain the information.
808     * @param list A list of key to value pairs
809     * @param fieldDelim A char which is used to separate key/value pairs
810     * @param valueDelim A char which is used to separate key and value
811     */
812    private void doEncode(StringBuilder output, Collection<URIParam> list, String fieldDelim, String valueDelim)
813    {
814        if(!list.isEmpty())
815        {
816            output.append(list.stream()
817                    .map(uriParam ->
818                        String.join(
819                                valueDelim,
820                                urlEncode(uriParam.getKey()),
821                                urlEncode(Objects.toString(uriParam.getValue()))))
822                    .collect(Collectors.joining(fieldDelim)));
823        }
824    }
825
826    /**
827     * If the type is PATH_INFO, then add name/value to the pathInfo
828     * hashtable.
829     * <p>
830     * If the type is QUERY_DATA, then add name/value to the queryData
831     * hashtable.
832     *
833     * @param type Type (PATH_INFO or QUERY_DATA) of insertion.
834     * @param name A String with the name to add.
835     * @param value A String with the value to add.
836     */
837    protected void add(int type,
838            String name,
839            String value)
840    {
841        URIParam uriParam = new URIParam(parserService.convertAndTrim(name), value);
842        dataVectors[type].add(uriParam); // Code so clean you can eat from...
843    }
844
845    /**
846     * Method for a quick way to add all the parameters in a
847     * ParameterParser.
848     *
849     * <p>If the type is P (0), then add name/value to the pathInfo
850     * hashtable.
851     *
852     * <p>If the type is Q (1), then add name/value to the queryData
853     * hashtable.
854     *
855     * @param type Type of insertion (@see #add(char type, String name, String value))
856     * @param pp A ParameterParser.
857     */
858    protected void add(int type,
859            ParameterParser pp)
860    {
861        for(String key : pp.keySet())
862        {
863            if (!key.equalsIgnoreCase(CGI_ACTION_PARAM) &&
864                    !key.equalsIgnoreCase(CGI_SCREEN_PARAM))
865            {
866                String[] values = pp.getStrings(key);
867                if(values != null)
868                {
869                    for (String value : values)
870                    {
871                        add(type, key, value);
872                    }
873                }
874                else
875                {
876                    add(type, key, "");
877                }
878            }
879        }
880    }
881
882    /**
883     * Method for a quick way to add all the parameters in a
884     * List with URIParam objects.
885     *
886     * <p>If the type is P (0), then add name/value to the pathInfo
887     * hashtable.
888     *
889     * <p>If the type is Q (1), then add name/value to the queryData
890     * hashtable.
891     *
892     * @param type Type of insertion (@see #add(char type, String name, String value))
893     * @param list A List of URIParam objects
894     */
895    protected void add(int type, List<URIParam> list)
896    {
897        dataVectors[type].addAll(list);
898    }
899
900    /**
901     * If the type is P (0), then remove name/value from the
902     * pathInfo hashtable.
903     *
904     * <p>If the type is Q (1), then remove name/value from the
905     * queryData hashtable.
906     *
907     * @param type Type (P or Q) of removal.
908     * @param name A String with the name to be removed.
909     */
910    protected void remove (int type, String name)
911    {
912        String key = parserService.convertAndTrim(name);
913
914        dataVectors[type].removeIf(uriParam -> key.equals(uriParam.getKey()));
915    }
916}