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 URIParamRIParam.html#URIParam">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 }