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