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.util.ArrayList; 25 import java.util.Collection; 26 import java.util.Iterator; 27 import java.util.List; 28 29 import org.apache.commons.lang3.StringUtils; 30 import org.apache.fulcrum.parser.ParameterParser; 31 import org.apache.fulcrum.parser.ParserService; 32 import org.apache.logging.log4j.LogManager; 33 import org.apache.logging.log4j.Logger; 34 import org.apache.turbine.services.TurbineServices; 35 import org.apache.turbine.util.RunData; 36 import org.apache.turbine.util.ServerData; 37 38 /** 39 * This class allows you to keep all the information needed for a single 40 * link at one place. It keeps your query data, path info, the server 41 * scheme, name, port and the script path. 42 * 43 * If you must generate a Turbine Link, use this class. 44 * 45 * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a> 46 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a> 47 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 48 * @version $Id: TurbineURI.java 1854688 2019-03-03 10:36:42Z tv $ 49 */ 50 51 public class TurbineURI 52 extends BaseURI 53 { 54 /** Logging */ 55 private static final Logger log = LogManager.getLogger(TurbineURI.class); 56 57 /** Contains the PathInfo and QueryData vectors */ 58 private List<URIParam> [] dataVectors = null; 59 60 /** Local reference to the parser service for URI parameter folding */ 61 private ParserService parserService; 62 63 /* 64 * ======================================================================== 65 * 66 * Constructors 67 * 68 * ======================================================================== 69 * 70 */ 71 72 /** 73 * Empty C'tor. Uses Turbine.getDefaultServerData(). 74 */ 75 public TurbineURI() 76 { 77 super(); 78 init(); 79 } 80 81 /** 82 * Constructor with a RunData object. 83 * 84 * @param runData A RunData object 85 */ 86 public TurbineURI(RunData runData) 87 { 88 super(runData); 89 init(); 90 } 91 92 /** 93 * Constructor, set explicit redirection. 94 * 95 * @param runData A RunData object 96 * @param redirect True if redirection allowed. 97 */ 98 public TurbineURI(RunData runData, boolean redirect) 99 { 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 URIParamRIParam.html#URIParam">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 }