1 package org.apache.turbine.util.uri;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.UnsupportedEncodingException;
23 import java.net.URLEncoder;
24 import java.nio.charset.Charset;
25 import java.nio.charset.IllegalCharsetNameException;
26 import java.nio.charset.UnsupportedCharsetException;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.stream.Collectors;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.fulcrum.parser.ParameterParser;
35 import org.apache.fulcrum.parser.ParserService;
36 import org.apache.logging.log4j.LogManager;
37 import org.apache.logging.log4j.Logger;
38 import org.apache.turbine.services.TurbineServices;
39 import org.apache.turbine.util.RunData;
40 import org.apache.turbine.util.ServerData;
41
42 /**
43 * This class allows you to keep all the information needed for a single
44 * link at one place. It keeps your query data, path info, the server
45 * scheme, name, port and the script path.
46 *
47 * If you must generate a Turbine Link, use this class.
48 *
49 * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
50 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
51 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
52 * @version $Id$
53 */
54
55 public class TurbineURI
56 extends BaseURI
57 {
58 /** Logging */
59 private static final Logger log = LogManager.getLogger(TurbineURI.class);
60
61 /** Contains the PathInfo and QueryData vectors */
62 private List<URIParam> [] dataVectors = null;
63
64 /** Local reference to the parser service for URI parameter folding */
65 private ParserService parserService;
66
67 /** URI Parameter encoding as defined by the parser service */
68 private Charset parameterEncoding;
69
70 /*
71 * ========================================================================
72 *
73 * Constructors
74 *
75 * ========================================================================
76 *
77 */
78
79 /**
80 * Empty C'tor. Uses Turbine.getDefaultServerData().
81 */
82 public TurbineURI()
83 {
84 super();
85 init();
86 }
87
88 /**
89 * Constructor with a RunData object.
90 *
91 * @param runData A RunData object
92 */
93 public TurbineURI(RunData runData)
94 {
95 super(runData);
96 init();
97 }
98
99 /**
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 }